View | Details | Raw Unified | Return to bug 38136
Collapse All | Expand All

(-)a/C4/Biblio.pm (-8 / +9 lines)
Lines 100-105 use C4::Linker; Link Here
100
use C4::OAI::Sets;
100
use C4::OAI::Sets;
101
101
102
use Koha::Logger;
102
use Koha::Logger;
103
use Koha::Cache::Memory::Lite;
103
use Koha::Caches;
104
use Koha::Caches;
104
use Koha::ClassSources;
105
use Koha::ClassSources;
105
use Koha::Authority::Types;
106
use Koha::Authority::Types;
Lines 1526-1540 sub GetAuthorisedValueDesc { Link Here
1526
1527
1527
        #---- itemtypes
1528
        #---- itemtypes
1528
        if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
1529
        if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
1529
            my $lang = C4::Languages::getlanguage;
1530
            my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
1530
            $lang //= 'en';
1531
            my $cache_key    = 'GetAuthorisedValueDesc:itemtypes';
1531
            $cache_key = 'itemtype:description:' . $lang;
1532
            my $itemtypes    = $memory_cache->get_from_cache($cache_key);
1532
            my $itypes = $cache->get_from_cache( $cache_key, { unsafe => 1 } );
1533
            unless ($itemtypes) {
1533
            if ( !$itypes ) {
1534
                $itemtypes = { map { $_->itemtype => $_ } Koha::ItemTypes->as_list };
1534
                $itypes = { map { $_->itemtype => $_->translated_description } Koha::ItemTypes->search()->as_list };
1535
                $memory_cache->set_in_cache( $cache_key, $itemtypes );
1535
                $cache->set_in_cache( $cache_key, $itypes );
1536
            }
1536
            }
1537
            return $itypes->{$value};
1537
            my $itemtype = $itemtypes->{$value};
1538
            return $itemtype ? $itemtype->translated_description : undef;
1538
        }
1539
        }
1539
1540
1540
        if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "cn_source" ) {
1541
        if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "cn_source" ) {
(-)a/Koha/ItemType.pm (-72 / +26 lines)
Lines 19-33 use Modern::Perl; Link Here
19
19
20
use C4::Koha qw( getitemtypeimagelocation );
20
use C4::Koha qw( getitemtypeimagelocation );
21
use C4::Languages;
21
use C4::Languages;
22
use Koha::Caches;
23
use Koha::Database;
22
use Koha::Database;
24
use Koha::CirculationRules;
23
use Koha::CirculationRules;
25
use Koha::Localizations;
26
24
27
use base qw(Koha::Object Koha::Object::Limit::Library);
25
use base qw(Koha::Object Koha::Object::Limit::Library);
28
26
29
my $cache = Koha::Caches->get_instance();
30
31
=head1 NAME
27
=head1 NAME
32
28
33
Koha::ItemType - Koha Item type Object class
29
Koha::ItemType - Koha Item type Object class
Lines 36-83 Koha::ItemType - Koha Item type Object class Link Here
36
32
37
=head2 Class methods
33
=head2 Class methods
38
34
39
=cut
40
41
=head3 store
42
43
ItemType specific store to ensure relevant caches are flushed on change
44
45
=cut
46
47
sub store {
48
    my ($self) = @_;
49
50
    my $flush = 0;
51
52
    if ( !$self->in_storage ) {
53
        $flush = 1;
54
    } else {
55
        my $self_from_storage = $self->get_from_storage;
56
        $flush = 1 if ( $self_from_storage->description ne $self->description );
57
    }
58
59
    $self = $self->SUPER::store;
60
61
    if ($flush) {
62
        my $key = "itemtype:description:en";
63
        $cache->clear_from_cache($key);
64
    }
65
66
    return $self;
67
}
68
69
=head2 delete
70
71
ItemType specific C<delete> to clear relevant caches on delete.
72
73
=cut
74
75
sub delete {
76
    my $self = shift @_;
77
    $cache->clear_from_cache('itemtype:description:en');
78
    $self->SUPER::delete(@_);
79
}
80
81
=head3 image_location
35
=head3 image_location
82
36
83
=cut
37
=cut
Lines 93-118 sub image_location { Link Here
93
47
94
sub translated_description {
48
sub translated_description {
95
    my ( $self, $lang ) = @_;
49
    my ( $self, $lang ) = @_;
96
    if ( my $translated_description = eval { $self->get_column('translated_description') } ) {
97
50
98
        # If the value has already been fetched (eg. from sarch_with_localization),
51
    my $localization = $self->localization( 'description', $lang || C4::Languages::getlanguage() );
99
        # do not search for it again
100
        # Note: This is a bit hacky but should be fast
101
        return $translated_description
102
            ? $translated_description
103
            : $self->description;
104
    }
105
    $lang ||= C4::Languages::getlanguage;
106
    my $translated_description = Koha::Localizations->search(
107
        {
108
            code   => $self->itemtype,
109
            entity => 'itemtypes',
110
            lang   => $lang
111
        }
112
    )->next;
113
    return $translated_description
114
        ? $translated_description->translation
115
        : $self->description;
116
}
52
}
117
53
118
=head3 translated_descriptions
54
=head3 translated_descriptions
Lines 121-139 sub translated_description { Link Here
121
57
122
sub translated_descriptions {
58
sub translated_descriptions {
123
    my ($self) = @_;
59
    my ($self) = @_;
124
    my @translated_descriptions = Koha::Localizations->search(
60
125
        {
126
            entity => 'itemtypes',
127
            code   => $self->itemtype,
128
        }
129
    )->as_list;
130
    return [
61
    return [
131
        map {
62
        map {
132
            {
63
            {
133
                lang        => $_->lang,
64
                lang        => $_->lang,
134
                translation => $_->translation,
65
                translation => $_->translation,
135
            }
66
            }
136
        } @translated_descriptions
67
        } $self->_result->description_localizations
137
    ];
68
    ];
138
}
69
}
139
70
Lines 235-243 sub to_api_mapping { Link Here
235
        rentalcharge_hourly          => 'hourly_rental_charge',
166
        rentalcharge_hourly          => 'hourly_rental_charge',
236
        rentalcharge_hourly_calendar => 'hourly_rental_charge_calendar',
167
        rentalcharge_hourly_calendar => 'hourly_rental_charge_calendar',
237
        bookable_itemtype            => 'bookable_itemtype',
168
        bookable_itemtype            => 'bookable_itemtype',
169
170
        # TODO Remove after having updated all code using unblessed translated_description
171
        translated_description => undef,
238
    };
172
    };
239
}
173
}
240
174
175
=head3 unblessed
176
177
See L<Koha::Object/unblessed>
178
179
Overridden to add a C<translated_description> key for backward compatibility.
180
This should not be relied on as it may be removed in the future.
181
182
=cut
183
184
# TODO Remove after having updated all code using unblessed translated_description
185
sub unblessed {
186
    my ($self) = @_;
187
188
    my $unblessed = $self->SUPER::unblessed();
189
190
    $unblessed->{translated_description} = $self->translated_description;
191
192
    return $unblessed;
193
}
194
241
=head2 Internal methods
195
=head2 Internal methods
242
196
243
=head3 _type
197
=head3 _type
(-)a/Koha/ItemTypes.pm (-18 / +16 lines)
Lines 32-39 Koha::ItemTypes - Koha ItemType Object set class Link Here
32
32
33
=head2 Class methods
33
=head2 Class methods
34
34
35
=cut
36
37
=head3 search_with_localization
35
=head3 search_with_localization
38
36
39
my $itemtypes = Koha::ItemTypes->search_with_localization
37
my $itemtypes = Koha::ItemTypes->search_with_localization
Lines 43-64 my $itemtypes = Koha::ItemTypes->search_with_localization Link Here
43
sub search_with_localization {
41
sub search_with_localization {
44
    my ( $self, $params, $attributes ) = @_;
42
    my ( $self, $params, $attributes ) = @_;
45
43
46
    my $language = C4::Languages::getlanguage();
44
    return $self->search( $params, $attributes )->order_by_translated_description;
47
    $Koha::Schema::Result::Itemtype::LANGUAGE = $language;
45
}
48
    $attributes->{order_by}                   = 'translated_description' unless exists $attributes->{order_by};
46
49
    $attributes->{join}                       = 'localization';
47
=head3 order_by_translated_description
50
    $attributes->{'+select'}                  = [
48
51
        {
49
=cut
52
            coalesce => [qw( localization.translation me.description )],
50
53
            -as      => 'translated_description'
51
sub order_by_translated_description {
54
        }
52
    my ($self) = @_;
55
    ];
53
56
    if ( defined $params->{branchcode} ) {
54
    my $attributes = {
57
        my $branchcode = delete $params->{branchcode};
55
        join     => 'description_localization',
58
        $self->search_with_library_limits( $params, $attributes, $branchcode );
56
        order_by => \['COALESCE(description_localization.translation, me.description)'],
59
    } else {
57
    };
60
        $self->SUPER::search( $params, $attributes );
58
61
    }
59
    return $self->search( {}, $attributes );
62
}
60
}
63
61
64
=head2 Internal methods
62
=head2 Internal methods
(-)a/Koha/Localization.pm (-73 lines)
Lines 1-73 Link Here
1
package Koha::Localization;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Koha::Database;
21
22
use base qw(Koha::Object);
23
24
my $cache = Koha::Caches->get_instance();
25
26
=head1 NAME
27
28
Koha::Localization - Koha Localization type Object class
29
30
=head1 API
31
32
=head2 Class methods
33
34
=cut
35
36
=head3 store
37
38
Localization specific store to ensure relevant caches are flushed on change
39
40
=cut
41
42
sub store {
43
    my ($self) = @_;
44
    $self = $self->SUPER::store;
45
46
    if ( $self->entity eq 'itemtypes' ) {
47
        my $key = "itemtype:description:" . $self->lang;
48
        $cache->clear_from_cache($key);
49
    }
50
51
    return $self;
52
}
53
54
=head2 delete
55
56
Localization specific C<delete> to clear relevant caches on delete.
57
58
=cut
59
60
sub delete {
61
    my $self = shift @_;
62
    if ( $self->entity eq 'itemtypes' ) {
63
        my $key = "itemtype:description:" . $self->lang;
64
        $cache->clear_from_cache($key);
65
    }
66
    $self->SUPER::delete(@_);
67
}
68
69
sub _type {
70
    return 'Localization';
71
}
72
73
1;
(-)a/Koha/Localizations.pm (-34 lines)
Lines 1-34 Link Here
1
package Koha::Localizations;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Koha::Database;
21
22
use Koha::Localization;
23
24
use base qw(Koha::Objects);
25
26
sub _type {
27
    return 'Localization';
28
}
29
30
sub object_class {
31
    return 'Koha::Localization';
32
}
33
34
1;
(-)a/Koha/Object.pm (+26 lines)
Lines 956-961 sub unblessed_all_relateds { Link Here
956
    return \%data;
956
    return \%data;
957
}
957
}
958
958
959
=head3 localization
960
961
Returns a localized (translated) value of the property, or the original
962
property value if no translation exist
963
964
    $localization = $object->localization($property, $lang);
965
966
C<$property> is the property name. Often this will correspond to an SQL column name
967
968
C<$lang> is the language code (for instance: 'en-GB')
969
970
=cut
971
972
sub localization {
973
    my ( $self, $property, $lang ) = @_;
974
975
    my $result = $self->_result;
976
    if ( $result->can('localization') ) {
977
        if ( my $localization = $result->localization( $property, $lang ) ) {
978
            return $localization;
979
        }
980
    }
981
982
    return $result->get_column($property);
983
}
984
959
=head3 $object->_result();
985
=head3 $object->_result();
960
986
961
Returns the internal DBIC Row object
987
Returns the internal DBIC Row object
(-)a/Koha/Schema/Component/Localization.pm (+214 lines)
Line 0 Link Here
1
package Koha::Schema::Component::Localization;
2
3
=head1 NAME
4
5
Koha::Schema::Component::Localization
6
7
=head1 SYNOPSIS
8
9
    package Koha::Schema::Result::SomeTable;
10
11
    # ... generated code ...
12
13
    __PACKAGE__->load_components('+Koha::Schema::Component::Localization');
14
15
    __PACKAGE__->localization_add_relationships(
16
        'some_table_localizations',
17
        'some_table_id' => 'some_table_id',
18
        'first_column',
19
        'second_column',
20
        # ...
21
    );
22
23
    package main;
24
25
    my $rows = $schema->resultset('SomeTable');
26
    my $row = $rows->first;
27
    $row->localizations->search($cond);
28
    $row->create_related('first_column_localizations', { lang => $lang, translation => $translation })
29
30
    while (my $row = $rows->next)
31
        # first call will fetch all localizations for the current language and
32
        # the result will be cached, next calls will not execute a query
33
        $row->localization('first_column', $lang);
34
35
        # no query executed
36
        $row->localization('second_column', $lang);
37
    }
38
39
=head1 DESCRIPTION
40
41
This is a DBIx::Class component that helps to manage database localizations by
42
adding several relationships and methods to a "Result Class"
43
44
This can handle several localizable columns (also referred as "properties") per
45
table
46
47
To add database localizations to an existing database table, you need to:
48
49
=over
50
51
=item * Create a new table with:
52
53
=over
54
55
=item * An auto incremented column as primary key
56
57
=item * A foreign key column referencing the existing table
58
59
=item * 3 string (varchar or text) columns named 'property', 'lang',
60
'translation'
61
62
=item * A unique key comprising the foreign key column, 'property' and 'lang'
63
64
=back
65
66
=item * Regenerate the DBIx::Class schema with
67
misc/devel/update_dbix_class_files.pl
68
69
=item * Add calls to load_components and localization_add_relationships at the
70
end of the result class
71
72
=back
73
74
This will give you a relationship named 'localizations' through which you can
75
access all localizations of a particular table row.
76
77
And for every property, you will have:
78
79
=over
80
81
=item * a "has_many" relationship named <property>_localizations, giving access
82
to all localizations of a particular table row for this particular property
83
84
=item * a "might_have" relationship named <property>_localization, giving
85
access to the localization of a particular table row for this particular
86
property and for the current language (uses C4::Languages::getlanguage)
87
88
=back
89
90
The "row" object will also gain a method C<localization($property, $lang)>
91
which returns a specific translation and uses cache to avoid executing lots of
92
queries
93
94
=cut
95
96
use Modern::Perl;
97
use Carp;
98
99
use base qw(DBIx::Class);
100
101
=head2 localization_add_relationships
102
103
Add relationships to the localization table
104
105
=cut
106
107
sub localization_add_relationships {
108
    my ( $class, $pk_column, @properties ) = @_;
109
110
    my $rel_class   = 'Koha::Schema::Result::Localization';
111
    my $source_name = $class =~ s/.*:://r;
112
113
    $class->has_many(
114
        'localizations',
115
        $rel_class,
116
        sub {
117
            my ($args) = @_;
118
119
            return (
120
                {
121
                    "$args->{foreign_alias}.code"   => { -ident => "$args->{self_alias}.$pk_column" },
122
                    "$args->{foreign_alias}.entity" => $source_name,
123
                },
124
                !$args->{self_result_object} ? () : {
125
                    "$args->{foreign_alias}.code"   => $args->{self_result_object}->get_column($pk_column),
126
                    "$args->{foreign_alias}.entity" => $source_name,
127
                },
128
            );
129
        },
130
        { cascade_copy => 0, cascade_delete => 1, cascade_update => 0 },
131
    );
132
133
    foreach my $property (@properties) {
134
        $class->might_have(
135
            $property . '_localization',
136
            $rel_class,
137
            sub {
138
                my ($args) = @_;
139
140
                # Not a 'use' because we don't want to load C4::Languages (and
141
                # thus C4::Context) while loading the schema
142
                require C4::Languages;
143
                my $lang = C4::Languages::getlanguage();
144
145
                return (
146
                    {
147
                        "$args->{foreign_alias}.code"     => { -ident => "$args->{self_alias}.$pk_column" },
148
                        "$args->{foreign_alias}.entity"   => $source_name,
149
                        "$args->{foreign_alias}.property" => $property,
150
                        "$args->{foreign_alias}.lang"     => $lang,
151
                    },
152
                    !$args->{self_result_object} ? () : {
153
                        "$args->{foreign_alias}.code"     => $args->{self_result_object}->get_column($pk_column),
154
                        "$args->{foreign_alias}.entity"   => $source_name,
155
                        "$args->{foreign_alias}.property" => $property,
156
                        "$args->{foreign_alias}.lang"     => $lang,
157
                    },
158
                );
159
            },
160
            { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 },
161
        );
162
163
        $class->has_many(
164
            $property . '_localizations',
165
            $rel_class,
166
            sub {
167
                my ($args) = @_;
168
169
                return (
170
                    {
171
                        "$args->{foreign_alias}.code"     => { -ident => "$args->{self_alias}.$pk_column" },
172
                        "$args->{foreign_alias}.entity"   => $source_name,
173
                        "$args->{foreign_alias}.property" => $property,
174
                    },
175
                    !$args->{self_result_object} ? () : {
176
                        "$args->{foreign_alias}.code"     => $args->{self_result_object}->get_column($pk_column),
177
                        "$args->{foreign_alias}.entity"   => $source_name,
178
                        "$args->{foreign_alias}.property" => $property,
179
                    },
180
                );
181
            },
182
            { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 },
183
        );
184
    }
185
}
186
187
sub localization {
188
    my ( $self, $property, $lang ) = @_;
189
190
    my $result_source = $self->result_source;
191
192
    my $cache             = Koha::Caches->get_instance('localization');
193
    my $cache_key         = sprintf( '%s:%s', $result_source->source_name, $lang );
194
    my $localizations_map = $cache->get_from_cache($cache_key);
195
    unless ($localizations_map) {
196
        $localizations_map = {};
197
198
        my $localizations = $result_source->schema->resultset('Localization')->search( { lang => $lang } );
199
        while ( my $localization = $localizations->next ) {
200
            my $fk               = $localization->get_column('code');
201
            my $localization_key = sprintf( '%s:%s', $fk, $localization->property );
202
            $localizations_map->{$localization_key} = $localization->translation;
203
        }
204
205
        $cache->set_in_cache( $cache_key, $localizations_map );
206
    }
207
208
    my ($pk) = $self->id;
209
    my $localization_key = sprintf( '%s:%s', $pk, $property );
210
211
    return $localizations_map->{$localization_key};
212
}
213
214
1;
(-)a/Koha/Schema/Result/Itemtype.pm (-18 / +4 lines)
Lines 344-351 __PACKAGE__->has_many( Link Here
344
);
344
);
345
345
346
346
347
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-10-25 13:25:14
347
# Created by DBIx::Class::Schema::Loader v0.07052 @ 2024-12-03 14:37:10
348
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:jd0dYE700dpg1IiRnfbcEg
348
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:l3ycM1L5bmCeM0Pby8GceA
349
349
350
__PACKAGE__->add_columns(
350
__PACKAGE__->add_columns(
351
    '+automatic_checkin'            => { is_boolean => 1 },
351
    '+automatic_checkin'            => { is_boolean => 1 },
Lines 356-377 __PACKAGE__->add_columns( Link Here
356
    '+bookable'                     => { is_boolean => 1, is_nullable => 1 },
356
    '+bookable'                     => { is_boolean => 1, is_nullable => 1 },
357
);
357
);
358
358
359
# Use the ItemtypeLocalization view to create the join on localization
359
__PACKAGE__->load_components('+Koha::Schema::Component::Localization');
360
our $LANGUAGE;
360
__PACKAGE__->localization_add_relationships('itemtype', 'description');
361
__PACKAGE__->has_many(
362
  "localization" => "Koha::Schema::Result::ItemtypeLocalization",
363
    sub {
364
        my $args = shift;
365
366
        die "no lang specified!" unless $LANGUAGE;
367
368
        return ({
369
            "$args->{self_alias}.itemtype" => { -ident => "$args->{foreign_alias}.code" },
370
            "$args->{foreign_alias}.lang" => $LANGUAGE,
371
        });
372
373
    }
374
);
375
361
376
sub koha_object_class {
362
sub koha_object_class {
377
    'Koha::ItemType';
363
    'Koha::ItemType';
(-)a/Koha/Schema/Result/ItemtypeLocalization.pm (-32 lines)
Lines 1-32 Link Here
1
package Koha::Schema::Result::ItemtypeLocalization;
2
3
use base 'DBIx::Class::Core';
4
5
use Modern::Perl;
6
7
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
8
9
__PACKAGE__->table('itemtype_localizations');
10
__PACKAGE__->result_source_instance->is_virtual(1);
11
__PACKAGE__->result_source_instance->view_definition(
12
    "SELECT localization_id, code, lang, translation FROM localization WHERE entity='itemtypes'"
13
);
14
15
__PACKAGE__->add_columns(
16
  "localization_id",
17
  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
18
  "code",
19
  { data_type => "varchar", is_nullable => 0, size => 64 },
20
  "lang",
21
  { data_type => "varchar", is_nullable => 0, size => 25 },
22
  "translation",
23
  { data_type => "text", is_nullable => 1 },
24
);
25
26
__PACKAGE__->belongs_to(
27
    "itemtype",
28
    "Koha::Schema::Result::Itemtype",
29
    { code => 'itemtype' }
30
);
31
32
1;
(-)a/Koha/UI/Form/Builder/Item.pm (-4 / +3 lines)
Lines 202-213 sub generate_subfield_form { Link Here
202
            }
202
            }
203
        } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
203
        } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
204
            push @authorised_values, "";
204
            push @authorised_values, "";
205
            my $itemtypes;
205
            my $itemtypes = Koha::ItemTypes->new;
206
            if ($branch_limit) {
206
            if ($branch_limit) {
207
                $itemtypes = Koha::ItemTypes->search_with_localization( { branchcode => $branch_limit } );
207
                $itemtypes = $itemtypes->search_with_library_limits( {}, {}, $branch_limit );
208
            } else {
209
                $itemtypes = Koha::ItemTypes->search_with_localization;
210
            }
208
            }
209
            $itemtypes = $itemtypes->order_by_translated_description;
211
            while ( my $itemtype = $itemtypes->next ) {
210
            while ( my $itemtype = $itemtypes->next ) {
212
                push @authorised_values, $itemtype->itemtype;
211
                push @authorised_values, $itemtype->itemtype;
213
                $authorised_lib{ $itemtype->itemtype } = $itemtype->translated_description;
212
                $authorised_lib{ $itemtype->itemtype } = $itemtype->translated_description;
(-)a/admin/itemtypes.pl (-1 lines)
Lines 31-37 use C4::Auth qw( get_template_and_user ); Link Here
31
use C4::Output qw( output_html_with_http_headers );
31
use C4::Output qw( output_html_with_http_headers );
32
use Koha::ItemTypes;
32
use Koha::ItemTypes;
33
use Koha::ItemType;
33
use Koha::ItemType;
34
use Koha::Localizations;
35
34
36
my $input         = CGI->new;
35
my $input         = CGI->new;
37
my $searchfield   = $input->param('description');
36
my $searchfield   = $input->param('description');
(-)a/admin/localization.pl (-16 / +19 lines)
Lines 20-27 use Modern::Perl; Link Here
20
use C4::Auth   qw( get_template_and_user );
20
use C4::Auth   qw( get_template_and_user );
21
use C4::Output qw( output_html_with_http_headers );
21
use C4::Output qw( output_html_with_http_headers );
22
22
23
use Koha::Localization;
23
use Koha::Database;
24
use Koha::Localizations;
24
25
my $schema = Koha::Database->schema;
25
26
26
use CGI qw( -utf8 );
27
use CGI qw( -utf8 );
27
28
Lines 36-54 my ( $template, $borrowernumber, $cookie ) = get_template_and_user( Link Here
36
    }
37
    }
37
);
38
);
38
39
39
my $entity = $query->param('entity');
40
my $source    = $query->param('source');
40
my $code   = $query->param('code');
41
my $object_id = $query->param('object_id');
41
my $rs     = Koha::Localizations->search( { entity => $entity, code => $code } );
42
my $property  = $query->param('property');
43
44
my $row = $schema->resultset($source)->find($object_id);
45
42
my @translations;
46
my @translations;
43
while ( my $s = $rs->next ) {
47
my $localizations = $row->localizations->search( { property => $property } );
44
    push @translations,
48
while ( my $localization = $localizations->next ) {
45
        {
49
    push @translations, {
46
        id          => $s->localization_id,
50
        localization_id => $localization->id,
47
        entity      => $s->entity,
51
        lang            => $localization->lang,
48
        code        => $s->code,
52
        translation     => $localization->translation,
49
        lang        => $s->lang,
53
    };
50
        translation => $s->translation,
51
        };
52
}
54
}
53
55
54
my $translated_languages = C4::Languages::getTranslatedLanguages();    # opac and intranet
56
my $translated_languages = C4::Languages::getTranslatedLanguages();    # opac and intranet
Lines 56-63 my $translated_languages = C4::Languages::getTranslatedLanguages(); # opac an Link Here
56
$template->param(
58
$template->param(
57
    translations => \@translations,
59
    translations => \@translations,
58
    languages    => $translated_languages,
60
    languages    => $translated_languages,
59
    entity       => $entity,
61
    source       => $source,
60
    code         => $code,
62
    object_id    => $object_id,
63
    property     => $property,
61
);
64
);
62
65
63
output_html_with_http_headers $query, $cookie, $template->output;
66
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/installer/data/mysql/atomicupdate/bug-38136.pl (+24 lines)
Line 0 Link Here
1
use Modern::Perl;
2
use Koha::Installer::Output qw(say_warning say_failure say_success say_info);
3
4
return {
5
    bug_number  => '38136',
6
    description => 'Add localization.property',
7
    up          => sub {
8
        my ($args) = @_;
9
        my ( $dbh, $out ) = @$args{qw(dbh out)};
10
11
        unless ( column_exists( 'localization', 'property' ) ) {
12
            $dbh->do("alter table `localization` add `property` varchar(100) null after `code`");
13
            $dbh->do("update `localization` set `property` = 'description', entity = 'Itemtype'");
14
            $dbh->do("alter table `localization` modify `property` varchar(100) not null");
15
            $dbh->do("alter table `localization` drop key `entity_code_lang`");
16
            $dbh->do(
17
                "alter table `localization` add unique key `entity_code_property_lang` (`entity`, `code`, `property`, `lang`)"
18
            );
19
20
            say_success( $out, 'Added column localization.property and updated localization.entity values' );
21
        }
22
23
    },
24
};
(-)a/installer/data/mysql/kohastructure.sql (-1 / +2 lines)
Lines 4488-4497 CREATE TABLE `localization` ( Link Here
4488
  `localization_id` int(11) NOT NULL AUTO_INCREMENT,
4488
  `localization_id` int(11) NOT NULL AUTO_INCREMENT,
4489
  `entity` varchar(16) NOT NULL,
4489
  `entity` varchar(16) NOT NULL,
4490
  `code` varchar(64) NOT NULL,
4490
  `code` varchar(64) NOT NULL,
4491
  `property` varchar(100) NOT NULL,
4491
  `lang` varchar(25) NOT NULL COMMENT 'could be a foreign key',
4492
  `lang` varchar(25) NOT NULL COMMENT 'could be a foreign key',
4492
  `translation` mediumtext DEFAULT NULL,
4493
  `translation` mediumtext DEFAULT NULL,
4493
  PRIMARY KEY (`localization_id`),
4494
  PRIMARY KEY (`localization_id`),
4494
  UNIQUE KEY `entity_code_lang` (`entity`,`code`,`lang`)
4495
  UNIQUE KEY `entity_code_property_lang` (`entity`,`code`,`property`,`lang`)
4495
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
4496
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
4496
/*!40101 SET character_set_client = @saved_cs_client */;
4497
/*!40101 SET character_set_client = @saved_cs_client */;
4497
4498
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/localization-link.inc (+3 lines)
Line 0 Link Here
1
<a href="/cgi-bin/koha/admin/localization.pl?source=[% source | uri %]&object_id=[% object_id | uri %]&property=[% property | uri %]" rel="gb_page_center[600,500]"
2
    ><i class="fa-solid fa-pencil" aria-hidden="true"></i> Translate into other languages</a
3
>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt (-3 / +1 lines)
Lines 194-202 Link Here
194
                        <label for="description" class="required">Description: </label>
194
                        <label for="description" class="required">Description: </label>
195
                        <input type="text" id="description" name="description" size="48" value="[% itemtype.description | html %]" required="required" /> <span class="required">Required</span>
195
                        <input type="text" id="description" name="description" size="48" value="[% itemtype.description | html %]" required="required" /> <span class="required">Required</span>
196
                        [% IF can_be_translated %]
196
                        [% IF can_be_translated %]
197
                            <a href="/cgi-bin/koha/admin/localization.pl?entity=itemtypes&code=[% itemtype.itemtype | uri %]" title="Translate item type [% itemtype.itemtype | html %]" rel="gb_page_center[600,500]"
197
                            [% INCLUDE 'localization-link.inc' source='Itemtype' object_id=itemtype.itemtype property='description' %]
198
                                ><i class="fa-solid fa-pencil" aria-hidden="true"></i> Translate into other languages</a
199
                            >
200
                        [% END %]
198
                        [% END %]
201
                    </li>
199
                    </li>
202
                    <li>
200
                    <li>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt (-36 / +47 lines)
Lines 18-37 Link Here
18
</head>
18
</head>
19
19
20
<body id="admin_localization" class="admin">
20
<body id="admin_localization" class="admin">
21
<div class="main container-fluid">
21
<div class="container-fluid">
22
    <div class="row">
22
    <div class="row">
23
        <div class="col-sm-12">
23
        <div class="col-sm-12">
24
            <h1>Localization</h1>
24
            <h1>Localization</h1>
25
            <form id="add_translation" method="get">
25
            <form id="add_translation" method="get">
26
                [% INCLUDE 'csrf-token.inc' %]
26
                [% INCLUDE 'csrf-token.inc' %]
27
                <input type="hidden" name="entity" value="[% entity | html %]" />
27
                <input type="hidden" name="source" value="[% source | html %]" />
28
                <input type="hidden" name="code" value="[% code | html %]" />
28
                <input type="hidden" name="object_id" value="[% object_id | html %]" />
29
                <input type="hidden" name="interface" value="[% interface_side | html %]" />
29
                <input type="hidden" name="property" value="[% property | html %]" />
30
                <fieldset class="rows clearfix">
30
                <fieldset class="rows clearfix">
31
                    <ol>
31
                    <ol>
32
                        <li>
32
                        <li>
33
                            <span class="label">Authorized value:</span>
33
                            <span class="label">Authorized value:</span>
34
                            [% code | html %]
34
                            [% object_id | html %]
35
                        </li>
35
                        </li>
36
                        <li>
36
                        <li>
37
                            <label for="lang">Language:</label>
37
                            <label for="lang">Language:</label>
Lines 76-84 Link Here
76
            <table id="localization">
76
            <table id="localization">
77
                <thead>
77
                <thead>
78
                    <tr>
78
                    <tr>
79
                        <th>Id</th>
80
                        <th>Entity</th>
81
                        <th>Code</th>
82
                        <th>Language</th>
79
                        <th>Language</th>
83
                        <th>Translation</th>
80
                        <th>Translation</th>
84
                        <th class="NoSort">&nbsp;</th>
81
                        <th class="NoSort">&nbsp;</th>
Lines 86-95 Link Here
86
                </thead>
83
                </thead>
87
                <tbody>
84
                <tbody>
88
                    [% FOR t IN translations %]
85
                    [% FOR t IN translations %]
89
                        <tr id="row_id_[% t.id | html %]" data-id="[% t.id | html %]">
86
                        <tr id="row_id_[% t.localization_id | html %]" data-id="[% t.localization_id | html %]">
90
                            <td>[% t.id | html %]</td>
91
                            <td>[% t.entity | html %]</td>
92
                            <td>[% t.code | html %]</td>
93
                            <td class="lang">[% t.lang | html %]</td>
87
                            <td class="lang">[% t.lang | html %]</td>
94
                            <td class="translation" contenteditable="true">[% t.translation | html %]</td>
88
                            <td class="translation" contenteditable="true">[% t.translation | html %]</td>
95
                            <td class="actions"
89
                            <td class="actions"
Lines 104-110 Link Here
104
    </div>
98
    </div>
105
    <!-- /.row -->
99
    <!-- /.row -->
106
</div>
100
</div>
107
<!-- /.main.container-fluid -->
101
<!-- /.container-fluid -->
108
102
109
[% MACRO jsinclude BLOCK %]
103
[% MACRO jsinclude BLOCK %]
110
    [% INCLUDE 'datatables.inc' %]
104
    [% INCLUDE 'datatables.inc' %]
Lines 116-122 Link Here
116
            var message;
110
            var message;
117
            if ( type == 'success_on_update' ) {
111
            if ( type == 'success_on_update' ) {
118
                message = $('<div class="alert alert-info"></div>');
112
                message = $('<div class="alert alert-info"></div>');
119
                message.text(_("Entity %s (code %s) for lang %s has correctly been updated with '%s'").format(data.entity, data.code, data.lang, data.translation));
113
                message.text(_("Translation for lang %s has correctly been updated with '%s'").format(data.lang, data.translation));
120
            } else if ( type == 'error_on_update' ) {
114
            } else if ( type == 'error_on_update' ) {
121
                message = $('<div class="alert alert-warning"></div>');
115
                message = $('<div class="alert alert-warning"></div>');
122
                if ( data.error_code == 'already_exists' ) {
116
                if ( data.error_code == 'already_exists' ) {
Lines 126-138 Link Here
126
                }
120
                }
127
            } else if ( type == 'success_on_delete' ) {
121
            } else if ( type == 'success_on_delete' ) {
128
                message = $('<div class="alert alert-info"></div>');
122
                message = $('<div class="alert alert-info"></div>');
129
                message.text(_("The translation (id %s) has been removed successfully").format(data.id));
123
                message.text(_("The translation has been removed successfully"));
130
            } else if ( type == 'error_on_delete' ) {
124
            } else if ( type == 'error_on_delete' ) {
131
                message = $('<div class="alert alert-warning"></div>');
125
                message = $('<div class="alert alert-warning"></div>');
132
                message.text(_("An error occurred when deleting this translation"));
126
                message.text(_("An error occurred when deleting this translation"));
133
            } else if ( type == 'success_on_insert' ) {
127
            } else if ( type == 'success_on_insert' ) {
134
                message = $('<div class="alert alert-info"></div>');
128
                message = $('<div class="alert alert-info"></div>');
135
                message.text(_("Translation (id %s) has been added successfully").format(data.id));
129
                message.text(_("Translation has been added successfully"));
136
            } else if ( type == 'error_on_insert' ) {
130
            } else if ( type == 'error_on_insert' ) {
137
                message = $('<div class="alert alert-warning"></div>');
131
                message = $('<div class="alert alert-warning"></div>');
138
                if ( data.error_code == 'already_exists' ) {
132
                if ( data.error_code == 'already_exists' ) {
Lines 149-162 Link Here
149
            }, 3000);
143
            }, 3000);
150
        }
144
        }
151
145
152
        function send_update_request( data, cell ) {
146
        function send_update_request( _data, cell ) {
147
            const form = document.forms.add_translation;
148
            const source = form.elements.source.value;
149
            const object_id = form.elements.object_id.value;
150
            const data = Object.assign({}, _data, { source, object_id });
153
            const client = APIClient.localization;
151
            const client = APIClient.localization;
154
            client.localizations.update(data).then(
152
            client.localizations.update(data).then(
155
                success => {
153
                success => {
156
                    if ( success.error ) {
154
                    if ( success.error ) {
157
                        $(cell).css('background-color', '#FF0000');
155
                        $(cell).css('background-color', '#FF0000');
158
                        show_message({ type: 'error_on_update', data: success });
156
                        show_message({ type: 'error_on_update', data: success });
159
                    } else if ( success.is_changed == 1 ) {
157
                    } else {
160
                        $(cell).css('background-color', '#00FF00');
158
                        $(cell).css('background-color', '#00FF00');
161
                        show_message({ type: 'success_on_update', data: success });
159
                        show_message({ type: 'success_on_update', data: success });
162
                    }
160
                    }
Lines 180-191 Link Here
180
            );
178
            );
181
        }
179
        }
182
180
183
        function send_delete_request( id, cell ) {
181
        function send_delete_request( localization_id, cell ) {
182
            const form = document.forms.add_translation;
183
            const source = form.elements.source.value;
184
            const object_id = form.elements.object_id.value;
185
            const property = form.elements.property.value;
186
            const data = { source, object_id, property, localization_id };
187
184
            const client = APIClient.localization;
188
            const client = APIClient.localization;
185
            client.localizations.delete(id).then(
189
            client.localizations.delete(data).then(
186
                success => {
190
                success => {
187
                    $("#localization").DataTable().row( '#row_id_' + id ).remove().draw();
191
                    $("#localization").DataTable().row( '#row_id_' + localization_id ).remove().draw();
188
                    show_message({ type: 'success_on_delete', data: {id} });
192
                    show_message({ type: 'success_on_delete', data: {localization_id} });
189
                },
193
                },
190
                error => {
194
                error => {
191
                    $(cell).css('background-color', '#FF9090');
195
                    $(cell).css('background-color', '#FF9090');
Lines 224-229 Link Here
224
            });
228
            });
225
            $("td.lang").on('click', function(){
229
            $("td.lang").on('click', function(){
226
                var td = this;
230
                var td = this;
231
                if (td.childElementCount > 0) {
232
                    // do nothing if there is already something there (like a select for instance)
233
                    return;
234
                }
235
227
                var lang = $(td).text();
236
                var lang = $(td).text();
228
                $(td).css('background-color', '');
237
                $(td).css('background-color', '');
229
                var my_select = $(languages_select).clone();
238
                var my_select = $(languages_select).clone();
Lines 233-242 Link Here
233
                });
242
                });
234
                $(my_select).on('change', function(){
243
                $(my_select).on('change', function(){
235
                    var tr = $(this).parent().parent();
244
                    var tr = $(this).parent().parent();
236
                    var id = $(tr).data('id');
245
                    var localization_id = $(tr).data('id');
237
                    var lang = $(this).find('option:selected').val();
246
                    var lang = $(this).find('option:selected').val();
238
                    var translation = $(this).text();
247
                    send_update_request( {localization_id, lang}, td );
239
                    send_update_request( {id, lang, translation}, td );
240
                });
248
                });
241
                $(my_select).on('blur', function(){
249
                $(my_select).on('blur', function(){
242
                    $(td).html(lang);
250
                    $(td).html(lang);
Lines 246-255 Link Here
246
254
247
            $("td.translation").on('blur', function(){
255
            $("td.translation").on('blur', function(){
248
                var tr = $(this).parent();
256
                var tr = $(this).parent();
249
                var id = $(tr).data('id');
257
                var localization_id = $(tr).data('id');
250
                var lang = $(tr).find('td.lang').text();
251
                var translation = $(this).text();
258
                var translation = $(this).text();
252
                send_update_request( {id, lang, translation}, this );
259
                send_update_request( {localization_id, translation}, this );
253
            });
260
            });
254
261
255
            $("body").on("click", "a.delete", function(e){
262
            $("body").on("click", "a.delete", function(e){
Lines 264-283 Link Here
264
271
265
            $("#add_translation").on('submit', function(e){
272
            $("#add_translation").on('submit', function(e){
266
                e.preventDefault();
273
                e.preventDefault();
267
                let localization = {
274
268
                    entity: $(this).find('input[name="entity"]').val(),
275
                const form = this;
269
                    code: $(this).find('input[name="code"]').val(),
276
                const source = form.elements.source.value;
270
                    lang: $(this).find('select[name="lang"] option:selected').val(),
277
                const object_id = form.elements.object_id.value;
271
                    translation: $(this).find('input[name="translation"]').val(),
278
                const property = form.elements.property.value;
272
                };
279
                const lang = form.elements.lang.value
280
                const translation = form.elements.translation.value
281
282
                let localization = { source, object_id, property, lang, translation };
273
                const client = APIClient.localization;
283
                const client = APIClient.localization;
274
                client.localizations.create(localization).then(
284
                client.localizations.create(localization).then(
275
                    success => {
285
                    success => {
276
                        if ( success.error ) {
286
                        if ( success.error ) {
277
                            show_message({ type: 'error_on_insert', data: success });
287
                            show_message({ type: 'error_on_insert', data: success });
278
                        } else {
288
                        } else {
279
                            var new_row = table.row.add( [ success.id, success.entity, success.code, success.lang, success.translation, "<a href=\"#\" class=\"delete\"><i class=\"fa fa-trash-can\"></i> Delete</a>" ] ).draw().node();
289
                            let delete_str = _("Delete");
280
                            $( new_row ).attr("id", "row_id_" + success.id ).data("id", success.id );
290
                            var new_row = table.api().row.add( [ success.lang, success.translation, "<a href=\"#\" class=\"delete\"><i class=\"fa fa-trash-can\"></i> %s</a>".format(escape_str(delete_str)) ] ).draw().node();
291
                            $( new_row ).attr("id", "row_id_" + success.localization_id ).data("id", success.localization_id );
281
                            show_message({ type: 'success_on_insert', data: success });
292
                            show_message({ type: 'success_on_insert', data: success });
282
                        }
293
                        }
283
                    },
294
                    },
(-)a/koha-tmpl/intranet-tmpl/prog/js/fetch/localization-api-client.js (-17 / +5 lines)
Lines 10-21 export class LocalizationAPIClient { Link Here
10
            create: localization =>
10
            create: localization =>
11
                this.httpClient.post({
11
                this.httpClient.post({
12
                    endpoint: "",
12
                    endpoint: "",
13
                    body: "entity=%s&code=%s&lang=%s&translation=%s".format(
13
                    body: new URLSearchParams(localization).toString(),
14
                        encodeURIComponent(localization.entity),
15
                        encodeURIComponent(localization.code),
16
                        encodeURIComponent(localization.lang),
17
                        encodeURIComponent(localization.translation)
18
                    ),
19
                    headers: {
14
                    headers: {
20
                        "Content-Type":
15
                        "Content-Type":
21
                            "application/x-www-form-urlencoded;charset=utf-8",
16
                            "application/x-www-form-urlencoded;charset=utf-8",
Lines 24-46 export class LocalizationAPIClient { Link Here
24
            update: localization =>
19
            update: localization =>
25
                this.httpClient.put({
20
                this.httpClient.put({
26
                    endpoint: "",
21
                    endpoint: "",
27
                    body: "id=%s&lang=%s&translation=%s".format(
22
                    body: new URLSearchParams(localization).toString(),
28
                        encodeURIComponent(localization.id),
29
                        encodeURIComponent(localization.lang),
30
                        encodeURIComponent(localization.translation)
31
                    ),
32
                    headers: {
23
                    headers: {
33
                        "Content-Type":
24
                        "Content-Type":
34
                            "application/x-www-form-urlencoded;charset=utf-8",
25
                            "application/x-www-form-urlencoded;charset=utf-8",
35
                    },
26
                    },
36
                }),
27
                }),
37
            delete: id =>
28
            delete: localization =>
38
                this.httpClient.delete({
29
                this.httpClient.delete({
39
                    endpoint: "/?id=%s".format(id),
30
                    endpoint:
40
                    headers: {
31
                        "?" + new URLSearchParams(localization).toString(),
41
                        "Content-Type":
42
                            "application/x-www-form-urlencoded;charset=utf-8",
43
                    },
44
                }),
32
                }),
45
        };
33
        };
46
    }
34
    }
(-)a/svc/localization (-79 / +82 lines)
Lines 2-112 Link Here
2
2
3
use Modern::Perl;
3
use Modern::Perl;
4
use Encode qw( encode );
4
use Encode qw( encode );
5
use Try::Tiny;
6
use JSON qw( to_json );
5
7
6
use C4::Service;
8
use C4::Service;
7
use Koha::Localizations;
9
use Koha::Caches;
10
use Koha::Database;
11
use C4::Output qw( output_with_http_headers );
8
12
9
our ( $query, $response ) = C4::Service->init( parameters => 'manage_itemtypes' );
13
our ( $query, $response ) = C4::Service->init( parameters => 'manage_itemtypes' );
10
14
11
sub get_translations {
12
    my $rs = Koha::Localizations->search( { entity => $query->param('entity'), code => $query->param('code') } );
13
    my @translations;
14
    while ( my $s = $rs->next ) {
15
        push @translations, {
16
            id          => $s->localization_id,
17
            entity      => $s->entity,
18
            code        => $s->code,
19
            lang        => $s->lang,
20
            translation => $s->translation,
21
        };
22
    }
23
    $response->param( translations => \@translations );
24
    C4::Service->return_success($response);
25
}
26
27
sub update_translation {
15
sub update_translation {
28
    my $id          = $query->param('id');
16
    my $source          = $query->param('source');
29
    my $translation = $query->param('translation');
17
    my $localization_id = $query->param('localization_id');
30
    my $lang        = $query->param('lang');
18
    my $object_id       = $query->param('object_id');
19
    my $lang            = $query->param('lang');
20
    my $translation     = $query->param('translation');
31
21
32
    my $localization = Koha::Localizations->find($id);
22
    my $schema = Koha::Database->schema;
33
    if ( defined $lang and $localization->lang ne $lang ) {
23
    my $row    = $schema->resultset($source)->find($object_id);
34
        $localization->lang($lang);
24
    if ($row) {
35
    }
25
        my $localization = $row->localizations->find($localization_id);
36
    if ( defined $translation and $localization->translation ne $translation ) {
26
        if ($localization) {
37
        $localization->translation($translation);
27
            try {
38
    }
28
                my $original_lang = $localization->lang;
39
    my %params;
29
                $localization->lang($lang)               if $lang;
40
    my $is_changed;
30
                $localization->translation($translation) if $translation;
41
    if ( $localization->is_changed ) {
31
                $localization->update();
42
        $is_changed = 1;
32
                Koha::Caches->get_instance('localization')->clear_from_cache("$source:$original_lang");
43
        unless (
33
                Koha::Caches->get_instance('localization')->clear_from_cache("$source:$lang") if $lang;
44
            Koha::Localizations->search(
34
            } catch {
45
                {
35
                $localization->discard_changes();
46
                    entity          => $localization->entity, code => $localization->code, lang => $lang,
36
                $response->param( error => 1, error_code => 'already_exists' );
47
                    localization_id => { '!=' => $localization->localization_id },
37
            };
48
                }
49
            )->count
50
            )
51
        {
52
            $localization->store;
53
        } else {
54
            $params{error}      = 1;
55
            $params{error_code} = 'already_exists';
56
        }
38
        }
39
40
        $response->param(
41
            lang        => $localization->lang,
42
            translation => $localization->translation,
43
        );
57
    }
44
    }
58
    $response->param(
45
59
        %params,
60
        id          => $localization->localization_id,
61
        entity      => $localization->entity,
62
        code        => $localization->code,
63
        lang        => $localization->lang,
64
        translation => $localization->translation,
65
        is_changed  => $is_changed,
66
    );
67
    C4::Service->return_success($response);
46
    C4::Service->return_success($response);
68
}
47
}
69
48
70
sub add_translation {
49
sub add_translation {
71
    my $entity      = $query->param('entity');
50
    my $source          = $query->param('source');
72
    my $code        = $query->param('code');
51
    my $localization_id = $query->param('localization_id');
73
    my $lang        = $query->param('lang');
52
    my $object_id       = $query->param('object_id');
74
    my $translation = $query->param('translation');
53
    my $property        = $query->param('property');
54
    my $lang            = $query->param('lang');
55
    my $translation     = $query->param('translation');
75
56
76
    unless ( Koha::Localizations->search( { entity => $entity, code => $code, lang => $lang, } )->count ) {
57
    my $schema = Koha::Database->schema;
77
        my $localization = Koha::Localization->new(
58
    my $row    = $schema->resultset($source)->find($object_id);
59
    try {
60
        my $localization = $row->create_related(
61
            "${property}_localizations",
78
            {
62
            {
79
                entity      => $entity,
63
                entity      => $source,
80
                code        => $code,
81
                lang        => $lang,
64
                lang        => $lang,
82
                translation => $translation,
65
                translation => $translation,
83
            }
66
            }
84
        );
67
        );
85
        $localization->store;
68
        Koha::Caches->get_instance('localization')->clear_from_cache("$source:$lang");
86
        $response->param(
69
87
            id          => $localization->localization_id,
70
        $response->param(
88
            entity      => $localization->entity,
71
            lang            => $localization->lang,
89
            code        => $localization->code,
72
            translation     => $localization->translation,
90
            lang        => $localization->lang,
73
            localization_id => $localization->id,
91
            translation => $localization->translation,
74
        );
92
        );
75
    } catch {
76
        $response->param( error => 1, error_code => 'already_exists' );
77
    };
93
78
94
    } else {
95
        $response->param( error => 1, error_code => 'already_exists', );
96
    }
97
    C4::Service->return_success($response);
79
    C4::Service->return_success($response);
98
}
80
}
99
81
100
sub delete_translation {
82
sub delete_translation {
101
    my $id = $query->param('id');
83
    my $source          = $query->param('source');
102
    Koha::Localizations->find($id)->delete;
84
    my $object_id       = $query->param('object_id');
103
    $response->param( id => $id );
85
    my $localization_id = $query->param('localization_id');
86
87
    my $schema = Koha::Database->schema;
88
    my $row    = $schema->resultset($source)->find($object_id);
89
    if ( $row && $row->can('localizations') ) {
90
        my $localization = $row->localizations->find($localization_id);
91
92
        unless ($localization) {
93
            my $json = to_json( { errors => 'Not found' } );
94
            output_with_http_headers $query, undef, $json, 'js', '404 Not Found';
95
            exit;
96
        }
97
98
        if ($localization) {
99
            $localization->delete();
100
            Koha::Caches->get_instance('localization')->clear_from_cache( "$source:" . $localization->lang );
101
        }
102
103
        $response->param(
104
            localization_id => $localization_id,
105
        );
106
    }
107
104
    C4::Service->return_success($response);
108
    C4::Service->return_success($response);
105
}
109
}
106
110
107
C4::Service->dispatch(
111
C4::Service->dispatch(
108
    [ 'GET /',    ['id'],                                      \&get_translations ],
112
    [ 'PUT /',    [], \&update_translation ],
109
    [ 'PUT /',    ['id'],                                      \&update_translation ],
113
    [ 'POST /',   [], \&add_translation ],
110
    [ 'POST /',   [ 'entity', 'code', 'lang', 'translation' ], \&add_translation ],
114
    [ 'DELETE /', [], \&delete_translation ],
111
    [ 'DELETE /', ['id'],                                      \&delete_translation ],
112
);
115
);
(-)a/t/db_dependent/Koha/Filter/ExpandCodedFields.t (-1 / +2 lines)
Lines 71-80 subtest 'ExpandCodedFields tests' => sub { Link Here
71
71
72
    # Clear GetAuthorisedValueDesc-generated cache
72
    # Clear GetAuthorisedValueDesc-generated cache
73
    $cache->clear_from_cache("libraries:name");
73
    $cache->clear_from_cache("libraries:name");
74
    $cache->clear_from_cache("itemtype:description:en");
75
    $cache->clear_from_cache("cn_sources:description");
74
    $cache->clear_from_cache("cn_sources:description");
76
    $cache->clear_from_cache( "AV_descriptions:" . $av->category );
75
    $cache->clear_from_cache( "AV_descriptions:" . $av->category );
77
76
77
    Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en');
78
78
    C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
79
    C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
79
    $biblio = Koha::Biblios->find( $biblio->biblionumber );
80
    $biblio = Koha::Biblios->find( $biblio->biblionumber );
80
    $record = $biblio->metadata->record;
81
    $record = $biblio->metadata->record;
(-)a/t/db_dependent/Koha/Item.t (-5 / +11 lines)
Lines 30-35 use C4::Biblio qw( GetMarcSubfieldStructure ); Link Here
30
use C4::Circulation qw( AddIssue AddReturn );
30
use C4::Circulation qw( AddIssue AddReturn );
31
31
32
use Koha::Caches;
32
use Koha::Caches;
33
use Koha::Cache::Memory::Lite;
33
use Koha::Items;
34
use Koha::Items;
34
use Koha::Database;
35
use Koha::Database;
35
use Koha::DateUtils qw( dt_from_string );
36
use Koha::DateUtils qw( dt_from_string );
Lines 2114-2123 subtest 'columns_to_str' => sub { Link Here
2114
    $cache->clear_from_cache("MarcStructure-1-");
2115
    $cache->clear_from_cache("MarcStructure-1-");
2115
    $cache->clear_from_cache("MarcSubfieldStructure-");
2116
    $cache->clear_from_cache("MarcSubfieldStructure-");
2116
    $cache->clear_from_cache("libraries:name");
2117
    $cache->clear_from_cache("libraries:name");
2117
    $cache->clear_from_cache("itemtype:description:en");
2118
    $cache->clear_from_cache("cn_sources:description");
2118
    $cache->clear_from_cache("cn_sources:description");
2119
    $cache->clear_from_cache("AV_descriptions:LOST");
2119
    $cache->clear_from_cache("AV_descriptions:LOST");
2120
2120
2121
    Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en');
2122
2121
    # Creating subfields 'é', 'è' that are not linked with a kohafield
2123
    # Creating subfields 'é', 'è' that are not linked with a kohafield
2122
    Koha::MarcSubfieldStructures->search(
2124
    Koha::MarcSubfieldStructures->search(
2123
        {
2125
        {
Lines 2199-2208 subtest 'columns_to_str' => sub { Link Here
2199
    $cache->clear_from_cache("MarcStructure-1-");
2201
    $cache->clear_from_cache("MarcStructure-1-");
2200
    $cache->clear_from_cache("MarcSubfieldStructure-");
2202
    $cache->clear_from_cache("MarcSubfieldStructure-");
2201
    $cache->clear_from_cache("libraries:name");
2203
    $cache->clear_from_cache("libraries:name");
2202
    $cache->clear_from_cache("itemtype:description:en");
2203
    $cache->clear_from_cache("cn_sources:description");
2204
    $cache->clear_from_cache("cn_sources:description");
2204
    $cache->clear_from_cache("AV_descriptions:LOST");
2205
    $cache->clear_from_cache("AV_descriptions:LOST");
2205
2206
2207
    Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en');
2208
2206
    $schema->storage->txn_rollback;
2209
    $schema->storage->txn_rollback;
2207
};
2210
};
2208
2211
Lines 2219-2228 subtest 'strings_map() tests' => sub { Link Here
2219
    $cache->clear_from_cache("MarcStructure-1-");
2222
    $cache->clear_from_cache("MarcStructure-1-");
2220
    $cache->clear_from_cache("MarcSubfieldStructure-");
2223
    $cache->clear_from_cache("MarcSubfieldStructure-");
2221
    $cache->clear_from_cache("libraries:name");
2224
    $cache->clear_from_cache("libraries:name");
2222
    $cache->clear_from_cache("itemtype:description:en");
2223
    $cache->clear_from_cache("cn_sources:description");
2225
    $cache->clear_from_cache("cn_sources:description");
2224
    $cache->clear_from_cache("AV_descriptions:LOST");
2226
    $cache->clear_from_cache("AV_descriptions:LOST");
2225
2227
2228
    Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en');
2229
2226
    # Recreating subfields just to be sure tests will be ok
2230
    # Recreating subfields just to be sure tests will be ok
2227
    # 1 => av (LOST)
2231
    # 1 => av (LOST)
2228
    # 3 => no link
2232
    # 3 => no link
Lines 2334-2339 subtest 'strings_map() tests' => sub { Link Here
2334
    )->store();
2338
    )->store();
2335
2339
2336
    Koha::Caches->get_instance->flush_all;
2340
    Koha::Caches->get_instance->flush_all;
2341
    Koha::Cache::Memory::Lite->get_instance->flush;
2337
2342
2338
    $item->set(
2343
    $item->set(
2339
        {
2344
        {
Lines 2406-2414 subtest 'strings_map() tests' => sub { Link Here
2406
    $cache->clear_from_cache("MarcStructure-1-");
2411
    $cache->clear_from_cache("MarcStructure-1-");
2407
    $cache->clear_from_cache("MarcSubfieldStructure-");
2412
    $cache->clear_from_cache("MarcSubfieldStructure-");
2408
    $cache->clear_from_cache("libraries:name");
2413
    $cache->clear_from_cache("libraries:name");
2409
    $cache->clear_from_cache("itemtype:description:en");
2410
    $cache->clear_from_cache("cn_sources:description");
2414
    $cache->clear_from_cache("cn_sources:description");
2411
2415
2416
    Koha::Caches->get_instance('localization')->clear_from_cache('Itemtype:en');
2417
2412
    $schema->storage->txn_rollback;
2418
    $schema->storage->txn_rollback;
2413
};
2419
};
2414
2420
Lines 2542-2548 subtest 'store() tests' => sub { Link Here
2542
            {
2548
            {
2543
                borrowernumber    => $patron->id,
2549
                borrowernumber    => $patron->id,
2544
                date              => '1970-01-01 14:00:01',
2550
                date              => '1970-01-01 14:00:01',
2545
                amountoutstanding => 0,
2551
                amountoutstanding =>  0,
2546
                amount            => -5,
2552
                amount            => -5,
2547
                interface         => 'commandline',
2553
                interface         => 'commandline',
2548
                credit_type_code  => 'PAYMENT'
2554
                credit_type_code  => 'PAYMENT'
(-)a/t/db_dependent/Koha/ItemTypes.t (-12 / +12 lines)
Lines 27-32 use t::lib::TestBuilder; Link Here
27
27
28
use C4::Calendar qw( new );
28
use C4::Calendar qw( new );
29
use Koha::Biblioitems;
29
use Koha::Biblioitems;
30
use Koha::Caches;
30
use Koha::Libraries;
31
use Koha::Libraries;
31
use Koha::Database;
32
use Koha::Database;
32
use Koha::DateUtils qw(dt_from_string);
33
use Koha::DateUtils qw(dt_from_string);
Lines 73-102 my $child3 = $builder->build_object( Link Here
73
    }
74
    }
74
);
75
);
75
76
76
Koha::Localization->new(
77
$child1->_result->localizations->create(
77
    {
78
    {
78
        entity      => 'itemtypes',
79
        property    => 'description',
79
        code        => $child1->itemtype,
80
        lang        => 'en',
80
        lang        => 'en',
81
        translation => 'b translated itemtype desc'
81
        translation => 'b translated itemtype desc'
82
    }
82
    }
83
)->store;
83
);
84
Koha::Localization->new(
84
$child2->_result->localizations->create(
85
    {
85
    {
86
        entity      => 'itemtypes',
86
        property    => 'description',
87
        code        => $child2->itemtype,
88
        lang        => 'en',
87
        lang        => 'en',
89
        translation => 'a translated itemtype desc'
88
        translation => 'a translated itemtype desc'
90
    }
89
    }
91
)->store;
90
);
92
Koha::Localization->new(
91
$child3->_result->localizations->create(
93
    {
92
    {
94
        entity      => 'something_else',
93
        property    => 'description',
95
        code        => $child2->itemtype,
96
        lang        => 'en',
94
        lang        => 'en',
97
        translation => 'another thing'
95
        translation => 'another thing'
98
    }
96
    }
99
)->store;
97
);
98
99
Koha::Caches->get_instance('localization')->flush_all();
100
100
101
my $type = Koha::ItemTypes->find( $child1->itemtype );
101
my $type = Koha::ItemTypes->find( $child1->itemtype );
102
ok( defined($type), 'first result' );
102
ok( defined($type), 'first result' );
(-)a/t/db_dependent/Koha/Template/Plugin/ItemTypes.t (-8 / +9 lines)
Lines 20-25 use Test::NoWarnings; Link Here
20
use Test::More tests => 11;
20
use Test::More tests => 11;
21
21
22
use C4::Context;
22
use C4::Context;
23
use Koha::Caches;
23
use Koha::Database;
24
use Koha::Database;
24
use Koha::ItemTypes;
25
use Koha::ItemTypes;
25
26
Lines 53-66 my $itemtypeA = $builder->build_object( Link Here
53
        }
54
        }
54
    }
55
    }
55
);
56
);
56
Koha::Localization->new(
57
$itemtypeA->_result->localizations->create(
57
    {
58
    {
58
        entity      => 'itemtypes',
59
        property    => 'description',
59
        code        => $itemtypeA->itemtype,
60
        lang        => 'en',
60
        lang        => 'en',
61
        translation => 'Translated itemtype A'
61
        translation => 'Translated itemtype A'
62
    }
62
    }
63
)->store;
63
);
64
my $itemtypeB = $builder->build_object(
64
my $itemtypeB = $builder->build_object(
65
    {
65
    {
66
        class => 'Koha::ItemTypes',
66
        class => 'Koha::ItemTypes',
Lines 70-83 my $itemtypeB = $builder->build_object( Link Here
70
        }
70
        }
71
    }
71
    }
72
);
72
);
73
Koha::Localization->new(
73
$itemtypeB->_result->localizations->create(
74
    {
74
    {
75
        entity      => 'itemtypes',
75
        property    => 'description',
76
        code        => $itemtypeB->itemtype,
77
        lang        => 'en',
76
        lang        => 'en',
78
        translation => 'Translated itemtype B'
77
        translation => 'Translated itemtype B'
79
    }
78
    }
80
)->store;
79
);
81
my $itemtypeC = $builder->build_object(
80
my $itemtypeC = $builder->build_object(
82
    {
81
    {
83
        class => 'Koha::ItemTypes',
82
        class => 'Koha::ItemTypes',
Lines 88-93 my $itemtypeC = $builder->build_object( Link Here
88
    }
87
    }
89
);
88
);
90
89
90
Koha::Caches->get_instance('localization')->flush_all();
91
91
my $GetDescriptionA1 = $plugin->GetDescription( $itemtypeA->itemtype );
92
my $GetDescriptionA1 = $plugin->GetDescription( $itemtypeA->itemtype );
92
is( $GetDescriptionA1, "Translated itemtype A", "ItemType without parent - GetDescription without want parent" );
93
is( $GetDescriptionA1, "Translated itemtype A", "ItemType without parent - GetDescription without want parent" );
93
my $GetDescriptionA2 = $plugin->GetDescription( $itemtypeA->itemtype, 1 );
94
my $GetDescriptionA2 = $plugin->GetDescription( $itemtypeA->itemtype, 1 );
(-)a/t/db_dependent/api/v1/item_types.t (-17 / +11 lines)
Lines 26-31 use t::lib::Mocks; Link Here
26
26
27
use Mojo::JSON qw(encode_json);
27
use Mojo::JSON qw(encode_json);
28
28
29
use Koha::Caches;
29
use Koha::ItemTypes;
30
use Koha::ItemTypes;
30
use Koha::Database;
31
use Koha::Database;
31
32
Lines 67-95 subtest 'list() tests' => sub { Link Here
67
        }
68
        }
68
    );
69
    );
69
70
70
    my $en = $builder->build_object(
71
    $item_type->_result->localizations->create(
71
        {
72
        {
72
            class => 'Koha::Localizations',
73
            property    => 'description',
73
            value => {
74
            lang        => 'en',
74
                entity      => 'itemtypes',
75
            translation => 'English word "test"',
75
                code        => $item_type->id,
76
                lang        => 'en',
77
                translation => 'English word "test"',
78
            }
79
        }
76
        }
80
    );
77
    );
81
    my $sv = $builder->build_object(
78
    $item_type->_result->localizations->create(
82
        {
79
        {
83
            class => 'Koha::Localizations',
80
            property    => 'description',
84
            value => {
81
            lang        => 'sv_SE',
85
                entity      => 'itemtypes',
82
            translation => 'Swedish word "test"',
86
                code        => $item_type->id,
87
                lang        => 'sv_SE',
88
                translation => 'Swedish word "test"',
89
            }
90
        }
83
        }
91
    );
84
    );
92
85
86
    Koha::Caches->get_instance('localization')->flush_all();
87
93
    my $librarian = $builder->build_object(
88
    my $librarian = $builder->build_object(
94
        {
89
        {
95
            class => 'Koha::Patrons',
90
            class => 'Koha::Patrons',
96
- 

Return to bug 38136