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

(-)a/Koha/DBIx/Class/Localization.pm (+200 lines)
Line 0 Link Here
1
package Koha::DBIx::Class::Localization;
2
3
=head1 NAME
4
5
Koha::DBIx::Class::Localization
6
7
=head1 SYNOPSIS
8
9
    package Koha::Schema::Result::SomeTable;
10
11
    # ... generated code ...
12
13
    __PACKAGE__->load_components('+Koha::DBIx::Class::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
98
use base qw(DBIx::Class);
99
100
sub localization_add_relationships {
101
    my ($class, $rel_name, $fk_column, $pk_column, @properties) = @_;
102
103
    my $rel_info = $class->relationship_info($rel_name);
104
105
    $class->has_many(
106
        'localizations',
107
        $rel_info->{class},
108
        { "foreign.$fk_column" => "self.$pk_column" },
109
        { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 },
110
    );
111
112
    foreach my $property (@properties) {
113
        $class->might_have(
114
            $property . "_localization",
115
            $rel_info->{class},
116
            sub {
117
                my ($args) = @_;
118
119
                # Not a 'use' because we don't want to load C4::Languages (and
120
                # thus C4::Context) while loading the schema
121
                require C4::Languages;
122
                my $lang = C4::Languages::getlanguage();
123
124
                return (
125
                    {
126
                        "$args->{foreign_alias}.$fk_column" => { -ident => "$args->{self_alias}.$pk_column" },
127
                        "$args->{foreign_alias}.property"   => $property,
128
                        "$args->{foreign_alias}.lang"       => $lang,
129
                    },
130
                    !$args->{self_result_object} ? () : {
131
                        "$args->{foreign_alias}.$fk_column" => $args->{self_result_object}->get_column($pk_column),
132
                        "$args->{foreign_alias}.property"   => $property,
133
                        "$args->{foreign_alias}.lang"       => $lang,
134
                    },
135
                    !$args->{foreign_values} ? () : {
136
                        "$args->{foreign_alias}.$fk_column" => $args->{foreign_values}->{$pk_column},
137
                    }
138
                );
139
            },
140
            { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 },
141
        );
142
143
        $class->has_many(
144
            $property . '_localizations',
145
            $rel_info->{class},
146
            sub {
147
                my ($args) = @_;
148
149
                return (
150
                    {
151
                        "$args->{foreign_alias}.$fk_column" => { -ident => "$args->{self_alias}.$pk_column" },
152
                        "$args->{foreign_alias}.property"   => $property,
153
                    },
154
                    !$args->{self_result_object} ? () : {
155
                        "$args->{foreign_alias}.$fk_column" => $args->{self_result_object}->get_column($pk_column),
156
                        "$args->{foreign_alias}.property"   => $property,
157
                    },
158
                    !$args->{foreign_values} ? () : {
159
                        "$args->{foreign_alias}.$fk_column" => $args->{foreign_values}->{$pk_column},
160
                    }
161
                );
162
            },
163
            { cascade_copy => 0, cascade_delete => 0, cascade_update => 0 },
164
        );
165
    }
166
}
167
168
sub localization {
169
    my ($self, $property, $lang) = @_;
170
171
    my $result_source = $self->result_source;
172
173
    my $cache = Koha::Caches->get_instance('localization');
174
    my $cache_key = sprintf('%s:%s', $result_source->source_name, $lang);
175
    my $localizations_map = $cache->get_from_cache($cache_key);
176
    unless ($localizations_map) {
177
        $localizations_map = {};
178
179
        my $rel_info = $self->relationship_info('localizations');
180
        my $rel_source = $rel_info->{class} =~ s/.*:://r;
181
182
        my ($fk_column) = map { s/^foreign\.//r } keys %{ $rel_info->{cond} };
183
184
        my $localizations = $result_source->schema->resultset($rel_source)->search({ lang => $lang });
185
        while (my $localization = $localizations->next) {
186
            my $fk = $localization->get_column($fk_column);
187
            my $localization_key = sprintf('%s:%s', $fk, $property);
188
            $localizations_map->{$localization_key} = $localization->translation;
189
        }
190
191
        $cache->set_in_cache($cache_key, $localizations_map);
192
    }
193
194
    my ($pk) = $self->id;
195
    my $localization_key = sprintf('%s:%s', $pk, $property);
196
197
    return $localizations_map->{$localization_key};
198
}
199
200
1;
(-)a/Koha/ItemType.pm (-70 / +18 lines)
Lines 22-28 use C4::Languages; Link Here
22
use Koha::Caches;
22
use Koha::Caches;
23
use Koha::Database;
23
use Koha::Database;
24
use Koha::CirculationRules;
24
use Koha::CirculationRules;
25
use Koha::Localizations;
26
25
27
use base qw(Koha::Object Koha::Object::Limit::Library);
26
use base qw(Koha::Object Koha::Object::Limit::Library);
28
27
Lines 36-83 Koha::ItemType - Koha Item type Object class Link Here
36
35
37
=head2 Class methods
36
=head2 Class methods
38
37
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
38
=head3 image_location
82
39
83
=cut
40
=cut
Lines 93-118 sub image_location { Link Here
93
50
94
sub translated_description {
51
sub translated_description {
95
    my ( $self, $lang ) = @_;
52
    my ( $self, $lang ) = @_;
96
    if ( my $translated_description = eval { $self->get_column('translated_description') } ) {
53
97
54
    my $localization = $self->localization('description', $lang || C4::Languages::getlanguage());
98
        # If the value has already been fetched (eg. from sarch_with_localization),
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
}
55
}
117
56
118
=head3 translated_descriptions
57
=head3 translated_descriptions
Lines 121-139 sub translated_description { Link Here
121
60
122
sub translated_descriptions {
61
sub translated_descriptions {
123
    my ($self) = @_;
62
    my ($self) = @_;
124
    my @translated_descriptions = Koha::Localizations->search(
63
125
        {
126
            entity => 'itemtypes',
127
            code   => $self->itemtype,
128
        }
129
    )->as_list;
130
    return [
64
    return [
131
        map {
65
        map {
132
            {
66
            {
133
                lang        => $_->lang,
67
                lang        => $_->lang,
134
                translation => $_->translation,
68
                translation => $_->translation,
135
            }
69
            }
136
        } @translated_descriptions
70
        } $self->_result->description_localizations
137
    ];
71
    ];
138
}
72
}
139
73
Lines 234-242 sub to_api_mapping { Link Here
234
        rentalcharge_daily_calendar  => 'daily_rental_charge_calendar',
168
        rentalcharge_daily_calendar  => 'daily_rental_charge_calendar',
235
        rentalcharge_hourly          => 'hourly_rental_charge',
169
        rentalcharge_hourly          => 'hourly_rental_charge',
236
        rentalcharge_hourly_calendar => 'hourly_rental_charge_calendar',
170
        rentalcharge_hourly_calendar => 'hourly_rental_charge_calendar',
171
172
        # TODO Remove after having updated all code using unblessed translated_description
173
        translated_description => undef,
237
    };
174
    };
238
}
175
}
239
176
177
# TODO Remove after having updated all code using unblessed translated_description
178
sub unblessed {
179
    my ($self) = @_;
180
181
    my $unblessed = $self->SUPER::unblessed();
182
183
    $unblessed->{translated_description} = $self->translated_description;
184
185
    return $unblessed;
186
}
187
240
=head2 Internal methods
188
=head2 Internal methods
241
189
242
=head3 _type
190
=head3 _type
(-)a/Koha/ItemTypes.pm (-18 / +16 lines)
Lines 33-40 Koha::ItemTypes - Koha ItemType Object set class Link Here
33
33
34
=head2 Class methods
34
=head2 Class methods
35
35
36
=cut
37
38
=head3 search_with_localization
36
=head3 search_with_localization
39
37
40
my $itemtypes = Koha::ItemTypes->search_with_localization
38
my $itemtypes = Koha::ItemTypes->search_with_localization
Lines 44-65 my $itemtypes = Koha::ItemTypes->search_with_localization Link Here
44
sub search_with_localization {
42
sub search_with_localization {
45
    my ( $self, $params, $attributes ) = @_;
43
    my ( $self, $params, $attributes ) = @_;
46
44
47
    my $language = C4::Languages::getlanguage();
45
    return $self->search( $params, $attributes )->order_by_translated_description;
48
    $Koha::Schema::Result::Itemtype::LANGUAGE = $language;
46
}
49
    $attributes->{order_by} = 'translated_description' unless exists $attributes->{order_by};
47
50
    $attributes->{join} = 'localization';
48
=head3 order_by_translated_description
51
    $attributes->{'+select'} = [
49
52
        {
50
=cut
53
            coalesce => [qw( localization.translation me.description )],
51
54
            -as      => 'translated_description'
52
sub order_by_translated_description {
55
        }
53
    my ($self) = @_;
56
    ];
54
57
    if(defined $params->{branchcode}) {
55
    my $attributes = {
58
        my $branchcode = delete $params->{branchcode};
56
        join     => 'description_localization',
59
        $self->search_with_library_limits( $params, $attributes, $branchcode );
57
        order_by => \['COALESCE(description_localization.translation, me.description)'],
60
    } else {
58
    };
61
        $self->SUPER::search( $params, $attributes );
59
62
    }
60
    return $self->search( {}, $attributes );
63
}
61
}
64
62
65
=head2 Internal methods
63
=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 (+27 lines)
Lines 907-912 sub unblessed_all_relateds { Link Here
907
    return \%data;
907
    return \%data;
908
}
908
}
909
909
910
=head3 localization
911
912
Returns a localized (translated) value of the property, or the original
913
property value if no translation exist
914
915
    $localization = $object->localization($property, $lang);
916
917
C<$property> is the property name. Often this will correspond to an SQL column name
918
919
C<$lang> is the language code (for instance: 'en-GB')
920
921
=cut
922
923
sub localization {
924
    my ($self, $property, $lang) = @_;
925
926
    my $result = $self->_result;
927
    if ($result->can('localization')) {
928
        if (my $localization = $result->localization($property, $lang)) {
929
            return $localization;
930
        }
931
    }
932
933
    return $result->get_column($property);
934
}
935
936
910
=head3 $object->_result();
937
=head3 $object->_result();
911
938
912
Returns the internal DBIC Row object
939
Returns the internal DBIC Row object
(-)a/Koha/Schema/Result/Itemtype.pm (-17 / +22 lines)
Lines 283-288 __PACKAGE__->has_many( Link Here
283
  { cascade_copy => 0, cascade_delete => 0 },
283
  { cascade_copy => 0, cascade_delete => 0 },
284
);
284
);
285
285
286
=head2 itemtypes_localizations
287
288
Type: has_many
289
290
Related object: L<Koha::Schema::Result::ItemtypesLocalization>
291
292
=cut
293
294
__PACKAGE__->has_many(
295
  "itemtypes_localizations",
296
  "Koha::Schema::Result::ItemtypesLocalization",
297
  { "foreign.itemtype" => "self.itemtype" },
298
  { cascade_copy => 0, cascade_delete => 0 },
299
);
300
286
=head2 old_reserves
301
=head2 old_reserves
287
302
288
Type: has_many
303
Type: has_many
Lines 334-341 __PACKAGE__->has_many( Link Here
334
);
349
);
335
350
336
351
337
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-05-08 14:38:07
352
# Created by DBIx::Class::Schema::Loader v0.07052 @ 2024-10-09 21:42:51
338
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9NChRQA4eBUqHpLXUFmOuw
353
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+/gmrTCXAvhf12UGXmC3yg
339
354
340
__PACKAGE__->add_columns(
355
__PACKAGE__->add_columns(
341
    '+automatic_checkin'            => { is_boolean => 1 },
356
    '+automatic_checkin'            => { is_boolean => 1 },
Lines 345-365 __PACKAGE__->add_columns( Link Here
345
    '+rentalcharge_hourly_calendar' => { is_boolean => 1 },
360
    '+rentalcharge_hourly_calendar' => { is_boolean => 1 },
346
);
361
);
347
362
348
# Use the ItemtypeLocalization view to create the join on localization
363
__PACKAGE__->load_components('+Koha::DBIx::Class::Localization');
349
our $LANGUAGE;
364
__PACKAGE__->localization_add_relationships(
350
__PACKAGE__->has_many(
365
    'itemtypes_localizations',
351
  "localization" => "Koha::Schema::Result::ItemtypeLocalization",
366
    'itemtype' => 'itemtype',
352
    sub {
367
    'description'
353
        my $args = shift;
354
355
        die "no lang specified!" unless $LANGUAGE;
356
357
        return ({
358
            "$args->{self_alias}.itemtype" => { -ident => "$args->{foreign_alias}.code" },
359
            "$args->{foreign_alias}.lang" => $LANGUAGE,
360
        });
361
362
    }
363
);
368
);
364
369
365
sub koha_object_class {
370
sub koha_object_class {
(-)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/Schema/Result/ItemtypesLocalization.pm (+124 lines)
Line 0 Link Here
1
use utf8;
2
package Koha::Schema::Result::ItemtypesLocalization;
3
4
# Created by DBIx::Class::Schema::Loader
5
# DO NOT MODIFY THE FIRST PART OF THIS FILE
6
7
=head1 NAME
8
9
Koha::Schema::Result::ItemtypesLocalization
10
11
=cut
12
13
use strict;
14
use warnings;
15
16
use base 'DBIx::Class::Core';
17
18
=head1 TABLE: C<itemtypes_localizations>
19
20
=cut
21
22
__PACKAGE__->table("itemtypes_localizations");
23
24
=head1 ACCESSORS
25
26
=head2 itemtypes_localizations_id
27
28
  data_type: 'integer'
29
  is_auto_increment: 1
30
  is_nullable: 0
31
32
=head2 itemtype
33
34
  data_type: 'varchar'
35
  is_foreign_key: 1
36
  is_nullable: 0
37
  size: 10
38
39
=head2 property
40
41
  data_type: 'varchar'
42
  is_nullable: 0
43
  size: 100
44
45
=head2 lang
46
47
  data_type: 'varchar'
48
  is_nullable: 0
49
  size: 25
50
51
=head2 translation
52
53
  data_type: 'mediumtext'
54
  is_nullable: 1
55
56
=cut
57
58
__PACKAGE__->add_columns(
59
  "itemtypes_localizations_id",
60
  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
61
  "itemtype",
62
  { data_type => "varchar", is_foreign_key => 1, is_nullable => 0, size => 10 },
63
  "property",
64
  { data_type => "varchar", is_nullable => 0, size => 100 },
65
  "lang",
66
  { data_type => "varchar", is_nullable => 0, size => 25 },
67
  "translation",
68
  { data_type => "mediumtext", is_nullable => 1 },
69
);
70
71
=head1 PRIMARY KEY
72
73
=over 4
74
75
=item * L</itemtypes_localizations_id>
76
77
=back
78
79
=cut
80
81
__PACKAGE__->set_primary_key("itemtypes_localizations_id");
82
83
=head1 UNIQUE CONSTRAINTS
84
85
=head2 C<itemtype_property_lang>
86
87
=over 4
88
89
=item * L</itemtype>
90
91
=item * L</property>
92
93
=item * L</lang>
94
95
=back
96
97
=cut
98
99
__PACKAGE__->add_unique_constraint("itemtype_property_lang", ["itemtype", "property", "lang"]);
100
101
=head1 RELATIONS
102
103
=head2 itemtype
104
105
Type: belongs_to
106
107
Related object: L<Koha::Schema::Result::Itemtype>
108
109
=cut
110
111
__PACKAGE__->belongs_to(
112
  "itemtype",
113
  "Koha::Schema::Result::Itemtype",
114
  { itemtype => "itemtype" },
115
  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
116
);
117
118
119
# Created by DBIx::Class::Schema::Loader v0.07052 @ 2024-10-08 14:32:00
120
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9xi1V/qaD/R9u3KHcW7saA
121
122
123
# You can replace this text with custom code or comments, and it will be preserved on regeneration
124
1;
(-)a/Koha/Schema/Result/Localization.pm (-108 lines)
Lines 1-108 Link Here
1
use utf8;
2
package Koha::Schema::Result::Localization;
3
4
# Created by DBIx::Class::Schema::Loader
5
# DO NOT MODIFY THE FIRST PART OF THIS FILE
6
7
=head1 NAME
8
9
Koha::Schema::Result::Localization
10
11
=cut
12
13
use strict;
14
use warnings;
15
16
use base 'DBIx::Class::Core';
17
18
=head1 TABLE: C<localization>
19
20
=cut
21
22
__PACKAGE__->table("localization");
23
24
=head1 ACCESSORS
25
26
=head2 localization_id
27
28
  data_type: 'integer'
29
  is_auto_increment: 1
30
  is_nullable: 0
31
32
=head2 entity
33
34
  data_type: 'varchar'
35
  is_nullable: 0
36
  size: 16
37
38
=head2 code
39
40
  data_type: 'varchar'
41
  is_nullable: 0
42
  size: 64
43
44
=head2 lang
45
46
  data_type: 'varchar'
47
  is_nullable: 0
48
  size: 25
49
50
could be a foreign key
51
52
=head2 translation
53
54
  data_type: 'mediumtext'
55
  is_nullable: 1
56
57
=cut
58
59
__PACKAGE__->add_columns(
60
  "localization_id",
61
  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
62
  "entity",
63
  { data_type => "varchar", is_nullable => 0, size => 16 },
64
  "code",
65
  { data_type => "varchar", is_nullable => 0, size => 64 },
66
  "lang",
67
  { data_type => "varchar", is_nullable => 0, size => 25 },
68
  "translation",
69
  { data_type => "mediumtext", is_nullable => 1 },
70
);
71
72
=head1 PRIMARY KEY
73
74
=over 4
75
76
=item * L</localization_id>
77
78
=back
79
80
=cut
81
82
__PACKAGE__->set_primary_key("localization_id");
83
84
=head1 UNIQUE CONSTRAINTS
85
86
=head2 C<entity_code_lang>
87
88
=over 4
89
90
=item * L</entity>
91
92
=item * L</code>
93
94
=item * L</lang>
95
96
=back
97
98
=cut
99
100
__PACKAGE__->add_unique_constraint("entity_code_lang", ["entity", "code", "lang"]);
101
102
103
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-01-21 13:39:29
104
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Elbup2i+1JON+xa38uzd6A
105
106
107
# You can replace this text with custom code or comments, and it will be preserved on regeneration
108
1;
(-)a/admin/itemtypes.pl (-1 lines)
Lines 32-38 use C4::Auth qw( get_template_and_user ); Link Here
32
use C4::Output qw( output_html_with_http_headers );
32
use C4::Output qw( output_html_with_http_headers );
33
use Koha::ItemTypes;
33
use Koha::ItemTypes;
34
use Koha::ItemType;
34
use Koha::ItemType;
35
use Koha::Localizations;
36
35
37
my $input         = CGI->new;
36
my $input         = CGI->new;
38
my $searchfield   = $input->param('description');
37
my $searchfield   = $input->param('description');
(-)a/admin/localization.pl (-15 / +19 lines)
Lines 21-28 use Modern::Perl; Link Here
21
use C4::Auth qw( get_template_and_user );
21
use C4::Auth qw( get_template_and_user );
22
use C4::Output qw( output_html_with_http_headers );
22
use C4::Output qw( output_html_with_http_headers );
23
23
24
use Koha::Localization;
24
use Koha::Database;
25
use Koha::Localizations;
25
26
my $schema = Koha::Database->schema;
26
27
27
use CGI qw( -utf8 );
28
use CGI qw( -utf8 );
28
29
Lines 36-53 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
      { id          => $s->localization_id,
49
    push @translations, {
46
        entity      => $s->entity,
50
        localization_id => $localization->id,
47
        code        => $s->code,
51
        lang            => $localization->lang,
48
        lang        => $s->lang,
52
        translation     => $localization->translation,
49
        translation => $s->translation,
53
    };
50
      };
51
}
54
}
52
55
53
my $translated_languages = C4::Languages::getTranslatedLanguages( 'intranet', C4::Context->preference('template') );
56
my $translated_languages = C4::Languages::getTranslatedLanguages( 'intranet', C4::Context->preference('template') );
Lines 55-62 my $translated_languages = C4::Languages::getTranslatedLanguages( 'intranet', C4 Link Here
55
$template->param(
58
$template->param(
56
    translations => \@translations,
59
    translations => \@translations,
57
    languages    => $translated_languages,
60
    languages    => $translated_languages,
58
    entity       => $entity,
61
    source       => $source,
59
    code         => $code,
62
    object_id    => $object_id,
63
    property     => $property,
60
);
64
);
61
65
62
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 (+36 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 itemtypes_localizations table',
7
    up          => sub {
8
        my ($args) = @_;
9
        my ( $dbh, $out ) = @$args{qw(dbh out)};
10
11
        unless ( TableExists('itemtypes_localizations') ) {
12
            $dbh->do( "
13
                CREATE TABLE `itemtypes_localizations` (
14
                  `itemtypes_localizations_id` int(11) NOT NULL AUTO_INCREMENT,
15
                  `itemtype` varchar(10) NOT NULL,
16
                  `property` varchar(100) NOT NULL,
17
                  `lang` varchar(25) NOT NULL,
18
                  `translation` mediumtext DEFAULT NULL,
19
                  PRIMARY KEY (`itemtypes_localizations_id`),
20
                  UNIQUE KEY `itemtype_property_lang` (`itemtype`, `property`, `lang`),
21
                  CONSTRAINT `itemtypes_localizations_itemtype_fk`
22
                    FOREIGN KEY (`itemtype`) REFERENCES `itemtypes` (`itemtype`)
23
                    ON DELETE CASCADE ON UPDATE CASCADE
24
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
25
            " );
26
27
            if ( TableExists('localization') ) {
28
                $dbh->do( "
29
                    INSERT INTO `itemtypes_localizations` (`itemtype`, `property`, `lang`, `translation`)
30
                    SELECT `code`, 'description', `lang`, `translation` FROM `localization` WHERE `entity` = 'itemtypes'
31
                " );
32
                $dbh->do('DROP TABLE localization');
33
            }
34
        }
35
    },
36
};
(-)a/installer/data/mysql/kohastructure.sql (+17 lines)
Lines 4166-4171 CREATE TABLE `itemtypes_branches` ( Link Here
4166
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
4166
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
4167
/*!40101 SET character_set_client = @saved_cs_client */;
4167
/*!40101 SET character_set_client = @saved_cs_client */;
4168
4168
4169
--
4170
-- Table structure for table `itemtypes_localizations`
4171
--
4172
4173
CREATE TABLE `itemtypes_localizations` (
4174
  `itemtypes_localizations_id` int(11) NOT NULL AUTO_INCREMENT,
4175
  `itemtype` varchar(10) NOT NULL,
4176
  `property` varchar(100) NOT NULL,
4177
  `lang` varchar(25) NOT NULL,
4178
  `translation` mediumtext DEFAULT NULL,
4179
  PRIMARY KEY (`itemtypes_localizations_id`),
4180
  UNIQUE KEY `itemtype_property_lang` (`itemtype`, `property`, `lang`),
4181
  CONSTRAINT `itemtypes_localizations_itemtype_fk`
4182
    FOREIGN KEY (`itemtype`) REFERENCES `itemtypes` (`itemtype`)
4183
    ON DELETE CASCADE ON UPDATE CASCADE
4184
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
4185
4169
--
4186
--
4170
-- Table structure for table `keyboard_shortcuts`
4187
-- Table structure for table `keyboard_shortcuts`
4171
--
4188
--
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/localization-link.inc (+1 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]"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Translate into other languages</a>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt (-1 / +1 lines)
Lines 192-198 Link Here
192
                    <label for="description" class="required">Description: </label>
192
                    <label for="description" class="required">Description: </label>
193
                    <input type="text" id="description" name="description" size="48" value="[% itemtype.description | html %]" required="required" /> <span class="required">Required</span>
193
                    <input type="text" id="description" name="description" size="48" value="[% itemtype.description | html %]" required="required" /> <span class="required">Required</span>
194
                    [% IF can_be_translated %]
194
                    [% IF can_be_translated %]
195
                        <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]"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Translate into other languages</a>
195
                        [% INCLUDE 'localization-link.inc' source='Itemtype' object_id=itemtype.itemtype property='description' %]
196
                    [% END %]
196
                    [% END %]
197
                </li>
197
                </li>
198
                <li>
198
                <li>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/localization.tt (-33 / +38 lines)
Lines 18-26 Link Here
18
                <h1>Localization</h1>
18
                <h1>Localization</h1>
19
<form id="add_translation" method="get">
19
<form id="add_translation" method="get">
20
    [% INCLUDE 'csrf-token.inc' %]
20
    [% INCLUDE 'csrf-token.inc' %]
21
    <input type="hidden" name="entity" value="[% entity | html %]" />
21
    <input type="hidden" name="source" value="[% source | html %]" />
22
    <input type="hidden" name="code" value="[% code | html %]" />
22
    <input type="hidden" name="object_id" value="[% object_id | html %]" />
23
    <input type="hidden" name="interface" value="[% interface_side | html %]" />
23
    <input type="hidden" name="property" value="[% property | html %]" />
24
    <fieldset class="rows clearfix">
24
    <fieldset class="rows clearfix">
25
        <ol>
25
        <ol>
26
            <li>
26
            <li>
Lines 66-74 Link Here
66
<table id="localization">
66
<table id="localization">
67
    <thead>
67
    <thead>
68
        <tr>
68
        <tr>
69
            <th>Id</th>
70
            <th>Entity</th>
71
            <th>Code</th>
72
            <th>Language</th>
69
            <th>Language</th>
73
            <th>Translation</th>
70
            <th>Translation</th>
74
            <th class="NoSort">&nbsp;</th>
71
            <th class="NoSort">&nbsp;</th>
Lines 76-85 Link Here
76
    </thead>
73
    </thead>
77
    <tbody>
74
    <tbody>
78
        [% FOR t IN translations %]
75
        [% FOR t IN translations %]
79
        <tr id="row_id_[% t.id | html %]" data-id="[% t.id | html %]">
76
        <tr id="row_id_[% t.localization_id | html %]" data-id="[% t.localization_id | html %]">
80
            <td>[% t.id | html %]</td>
81
            <td>[% t.entity | html %]</td>
82
            <td>[% t.code | html %]</td>
83
            <td class="lang">[% t.lang | html %]</td>
77
            <td class="lang">[% t.lang | html %]</td>
84
            <td class="translation" contenteditable="true">[% t.translation | html %]</td>
78
            <td class="translation" contenteditable="true">[% t.translation | html %]</td>
85
            <td class="actions"><a href="#" class="delete"><i class="fa fa-trash-can"></i> Delete</a></td>
79
            <td class="actions"><a href="#" class="delete"><i class="fa fa-trash-can"></i> Delete</a></td>
Lines 102-108 Link Here
102
            var message;
96
            var message;
103
            if ( type == 'success_on_update' ) {
97
            if ( type == 'success_on_update' ) {
104
                message = $('<div class="alert alert-info"></div>');
98
                message = $('<div class="alert alert-info"></div>');
105
                message.text(_("Entity %s (code %s) for lang %s has correctly been updated with '%s'").format(data.entity, data.code, data.lang, data.translation));
99
                message.text(_("Translation for lang %s has correctly been updated with '%s'").format(data.lang, data.translation));
106
            } else if ( type == 'error_on_update' ) {
100
            } else if ( type == 'error_on_update' ) {
107
                message = $('<div class="alert alert-warning"></div>');
101
                message = $('<div class="alert alert-warning"></div>');
108
                if ( data.error_code == 'already_exists' ) {
102
                if ( data.error_code == 'already_exists' ) {
Lines 112-124 Link Here
112
                }
106
                }
113
            } else if ( type == 'success_on_delete' ) {
107
            } else if ( type == 'success_on_delete' ) {
114
                message = $('<div class="alert alert-info"></div>');
108
                message = $('<div class="alert alert-info"></div>');
115
                message.text(_("The translation (id %s) has been removed successfully").format(data.id));
109
                message.text(_("The translation has been removed successfully"));
116
            } else if ( type == 'error_on_delete' ) {
110
            } else if ( type == 'error_on_delete' ) {
117
                message = $('<div class="alert alert-warning"></div>');
111
                message = $('<div class="alert alert-warning"></div>');
118
                message.text(_("An error occurred when deleting this translation"));
112
                message.text(_("An error occurred when deleting this translation"));
119
            } else if ( type == 'success_on_insert' ) {
113
            } else if ( type == 'success_on_insert' ) {
120
                message = $('<div class="alert alert-info"></div>');
114
                message = $('<div class="alert alert-info"></div>');
121
                message.text(_("Translation (id %s) has been added successfully").format(data.id));
115
                message.text(_("Translation has been added successfully"));
122
            } else if ( type == 'error_on_insert' ) {
116
            } else if ( type == 'error_on_insert' ) {
123
                message = $('<div class="alert alert-warning"></div>');
117
                message = $('<div class="alert alert-warning"></div>');
124
                if ( data.error_code == 'already_exists' ) {
118
                if ( data.error_code == 'already_exists' ) {
Lines 135-148 Link Here
135
            }, 3000);
129
            }, 3000);
136
        }
130
        }
137
131
138
        function send_update_request( data, cell ) {
132
        function send_update_request( _data, cell ) {
133
            const form = document.forms.add_translation;
134
            const source = form.elements.source.value;
135
            const object_id = form.elements.object_id.value;
136
            const data = Object.assign({}, _data, { source, object_id });
139
            const client = APIClient.localization;
137
            const client = APIClient.localization;
140
            client.localizations.update(data).then(
138
            client.localizations.update(data).then(
141
                success => {
139
                success => {
142
                    if ( success.error ) {
140
                    if ( success.error ) {
143
                        $(cell).css('background-color', '#FF0000');
141
                        $(cell).css('background-color', '#FF0000');
144
                        show_message({ type: 'error_on_update', data: success });
142
                        show_message({ type: 'error_on_update', data: success });
145
                    } else if ( success.is_changed == 1 ) {
143
                    } else {
146
                        $(cell).css('background-color', '#00FF00');
144
                        $(cell).css('background-color', '#00FF00');
147
                        show_message({ type: 'success_on_update', data: success });
145
                        show_message({ type: 'success_on_update', data: success });
148
                    }
146
                    }
Lines 166-177 Link Here
166
            );
164
            );
167
        }
165
        }
168
166
169
        function send_delete_request( id, cell ) {
167
        function send_delete_request( localization_id, cell ) {
168
            const form = document.forms.add_translation;
169
            const source = form.elements.source.value;
170
            const object_id = form.elements.object_id.value;
171
            const property = form.elements.property.value;
172
            const data = { source, object_id, property, localization_id };
173
170
            const client = APIClient.localization;
174
            const client = APIClient.localization;
171
            client.localizations.delete(id).then(
175
            client.localizations.delete(data).then(
172
                success => {
176
                success => {
173
                    $("#localization").DataTable().row( '#row_id_' + id ).remove().draw();
177
                    $("#localization").DataTable().row( '#row_id_' + localization_id ).remove().draw();
174
                    show_message({ type: 'success_on_delete', data: {id} });
178
                    show_message({ type: 'success_on_delete', data: {localization_id} });
175
                },
179
                },
176
                error => {
180
                error => {
177
                    $(cell).css('background-color', '#FF9090');
181
                    $(cell).css('background-color', '#FF9090');
Lines 221-230 Link Here
221
                });
225
                });
222
                $(my_select).on('change', function(){
226
                $(my_select).on('change', function(){
223
                    var tr = $(this).parent().parent();
227
                    var tr = $(this).parent().parent();
224
                    var id = $(tr).data('id');
228
                    var localization_id = $(tr).data('id');
225
                    var lang = $(this).find('option:selected').val();
229
                    var lang = $(this).find('option:selected').val();
226
                    var translation = $(this).text();
230
                    send_update_request( {localization_id, lang}, td );
227
                    send_update_request( {id, lang, translation}, td );
228
                });
231
                });
229
                $(my_select).on('blur', function(){
232
                $(my_select).on('blur', function(){
230
                    $(td).html(lang);
233
                    $(td).html(lang);
Lines 234-243 Link Here
234
237
235
            $("td.translation").on('blur', function(){
238
            $("td.translation").on('blur', function(){
236
                var tr = $(this).parent();
239
                var tr = $(this).parent();
237
                var id = $(tr).data('id');
240
                var localization_id = $(tr).data('id');
238
                var lang = $(tr).find('td.lang').text();
239
                var translation = $(this).text();
241
                var translation = $(this).text();
240
                send_update_request( {id, lang, translation}, this );
242
                send_update_request( {localization_id, translation}, this );
241
            });
243
            });
242
244
243
            $("body").on("click", "a.delete", function(e){
245
            $("body").on("click", "a.delete", function(e){
Lines 252-271 Link Here
252
254
253
            $("#add_translation").on('submit', function(e){
255
            $("#add_translation").on('submit', function(e){
254
                e.preventDefault();
256
                e.preventDefault();
255
                let localization = {
257
256
                    entity: $(this).find('input[name="entity"]').val(),
258
                const form = this;
257
                    code: $(this).find('input[name="code"]').val(),
259
                const source = form.elements.source.value;
258
                    lang: $(this).find('select[name="lang"] option:selected').val(),
260
                const object_id = form.elements.object_id.value;
259
                    translation: $(this).find('input[name="translation"]').val(),
261
                const property = form.elements.property.value;
260
                };
262
                const lang = form.elements.lang.value
263
                const translation = form.elements.translation.value
264
265
                let localization = { source, object_id, property, lang, translation };
261
                const client = APIClient.localization;
266
                const client = APIClient.localization;
262
                client.localizations.create(localization).then(
267
                client.localizations.create(localization).then(
263
                    success => {
268
                    success => {
264
                        if ( success.error ) {
269
                        if ( success.error ) {
265
                            show_message({ type: 'error_on_insert', data: success });
270
                            show_message({ type: 'error_on_insert', data: success });
266
                        } else {
271
                        } else {
267
                            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();
272
                            var new_row = table.row.add( [ success.lang, success.translation, "<a href=\"#\" class=\"delete\"><i class=\"fa fa-trash-can\"></i> Delete</a>" ] ).draw().node();
268
                            $( new_row ).attr("id", "row_id_" + success.id ).data("id", success.id );
273
                            $( new_row ).attr("id", "row_id_" + success.localization_id ).data("id", success.localization_id );
269
                            show_message({ type: 'success_on_insert', data: success });
274
                            show_message({ type: 'success_on_insert', data: success });
270
                        }
275
                        }
271
                    },
276
                    },
(-)a/koha-tmpl/intranet-tmpl/prog/js/fetch/localization-api-client.js (-17 / +4 lines)
Lines 13-24 export class LocalizationAPIClient extends HttpClient { Link Here
13
            create: localization =>
13
            create: localization =>
14
                this.post({
14
                this.post({
15
                    endpoint: "",
15
                    endpoint: "",
16
                    body: "entity=%s&code=%s&lang=%s&translation=%s".format(
16
                    body: (new URLSearchParams(localization)).toString(),
17
                        encodeURIComponent(localization.entity),
18
                        encodeURIComponent(localization.code),
19
                        encodeURIComponent(localization.lang),
20
                        encodeURIComponent(localization.translation)
21
                    ),
22
                    headers: {
17
                    headers: {
23
                        "Content-Type":
18
                        "Content-Type":
24
                            "application/x-www-form-urlencoded;charset=utf-8",
19
                            "application/x-www-form-urlencoded;charset=utf-8",
Lines 27-49 export class LocalizationAPIClient extends HttpClient { Link Here
27
            update: localization =>
22
            update: localization =>
28
                this.put({
23
                this.put({
29
                    endpoint: "",
24
                    endpoint: "",
30
                    body: "id=%s&lang=%s&translation=%s".format(
25
                    body: (new URLSearchParams(localization)).toString(),
31
                        encodeURIComponent(localization.id),
32
                        encodeURIComponent(localization.lang),
33
                        encodeURIComponent(localization.translation)
34
                    ),
35
                    headers: {
26
                    headers: {
36
                        "Content-Type":
27
                        "Content-Type":
37
                            "application/x-www-form-urlencoded;charset=utf-8",
28
                            "application/x-www-form-urlencoded;charset=utf-8",
38
                    },
29
                    },
39
                }),
30
                }),
40
            delete: id =>
31
            delete: localization =>
41
                this.delete({
32
                this.delete({
42
                    endpoint: "/?id=%s".format(id),
33
                    endpoint: "?" + (new URLSearchParams(localization)).toString(),
43
                    headers: {
44
                        "Content-Type":
45
                            "application/x-www-form-urlencoded;charset=utf-8",
46
                    },
47
                }),
34
                }),
48
        };
35
        };
49
    }
36
    }
(-)a/svc/localization (-64 / +63 lines)
Lines 2-104 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;
5
6
6
use C4::Service;
7
use C4::Service;
7
use Koha::Localizations;
8
use Koha::Caches;
9
use Koha::Database;
8
10
9
our ( $query, $response ) = C4::Service->init( parameters => 'manage_itemtypes' );
11
our ( $query, $response ) = C4::Service->init( parameters => 'manage_itemtypes' );
10
12
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 {
13
sub update_translation {
28
    my $id = $query->param('id');
14
    my $source = $query->param('source');
29
    my $translation = $query->param('translation');
15
    my $localization_id = $query->param('localization_id');
16
    my $object_id = $query->param('object_id');
30
    my $lang = $query->param('lang');
17
    my $lang = $query->param('lang');
18
    my $translation = $query->param('translation');
31
19
32
    my $localization = Koha::Localizations->find( $id );
20
    my $schema = Koha::Database->schema;
33
    if ( defined $lang and $localization->lang ne $lang ) {
21
    my $row = $schema->resultset($source)->find($object_id);
34
        $localization->lang( $lang )
22
    if ($row) {
35
    }
23
        my $localization = $row->localizations->find($localization_id);
36
    if ( defined $translation and $localization->translation ne $translation ) {
24
        if ($localization) {
37
        $localization->translation( $translation )
25
            try {
38
    }
26
                my $original_lang = $localization->lang;
39
    my %params;
27
                $localization->lang($lang) if $lang;
40
    my $is_changed;
28
                $localization->translation($translation) if $translation;
41
    if ( $localization->is_changed ) {
29
                $localization->update();
42
        $is_changed = 1;
30
                Koha::Caches->get_instance('localization')->clear_from_cache("$source:$lang");
43
        unless ( Koha::Localizations->search( { entity => $localization->entity, code => $localization->code, lang => $lang, localization_id => { '!=' => $localization->localization_id }, } )->count ) {
31
            } catch {
44
            $localization->store;
32
                $localization->discard_changes();
45
        } else {
33
                $response->param(error => 1, error_code => 'already_exists');
46
            $params{error} = 1;
34
            };
47
            $params{error_code} = 'already_exists';
48
        }
35
        }
36
37
        $response->param(
38
            lang        => $localization->lang,
39
            translation => $localization->translation,
40
        );
49
    }
41
    }
50
    $response->param(
42
51
        %params,
52
        id          => $localization->localization_id,
53
        entity      => $localization->entity,
54
        code        => $localization->code,
55
        lang        => $localization->lang,
56
        translation => $localization->translation,
57
        is_changed  => $is_changed,
58
    );
59
    C4::Service->return_success( $response );
43
    C4::Service->return_success( $response );
60
}
44
}
61
45
62
sub add_translation {
46
sub add_translation {
63
    my $entity = $query->param('entity');
47
    my $source = $query->param('source');
64
    my $code = $query->param('code');
48
    my $localization_id = $query->param('localization_id');
49
    my $object_id = $query->param('object_id');
50
    my $property = $query->param('property');
65
    my $lang = $query->param('lang');
51
    my $lang = $query->param('lang');
66
    my $translation = $query->param('translation');
52
    my $translation = $query->param('translation');
67
53
68
    unless ( Koha::Localizations->search({entity => $entity, code => $code, lang => $lang, })->count ) {
54
    my $schema = Koha::Database->schema;
69
        my $localization = Koha::Localization->new(
55
    my $row = $schema->resultset($source)->find($object_id);
56
    try {
57
        my $localization = $row->create_related(
58
            "${property}_localizations",
70
            {
59
            {
71
                entity => $entity,
72
                code => $code,
73
                lang => $lang,
60
                lang => $lang,
74
                translation => $translation,
61
                translation => $translation,
75
            }
62
            }
76
        );
63
        );
77
        $localization->store;
64
        Koha::Caches->get_instance('localization')->clear_from_cache("$source:$lang");
65
78
        $response->param(
66
        $response->param(
79
            id          => $localization->localization_id,
80
            entity      => $localization->entity,
81
            code        => $localization->code,
82
            lang        => $localization->lang,
67
            lang        => $localization->lang,
83
            translation => $localization->translation,
68
            translation => $localization->translation,
84
        );
69
        );
70
    } catch {
71
        $response->param(error => 1, error_code => 'already_exists');
72
    };
85
73
86
    } else {
87
        $response->param( error => 1, error_code => 'already_exists', );
88
    }
89
    C4::Service->return_success( $response );
74
    C4::Service->return_success( $response );
90
}
75
}
91
76
92
sub delete_translation {
77
sub delete_translation {
93
    my $id = $query->param('id');
78
    my $source          = $query->param('source');
94
    Koha::Localizations->find($id)->delete;
79
    my $object_id       = $query->param('object_id');
95
    $response->param( id => $id );
80
    my $localization_id = $query->param('localization_id');
81
82
    my $schema = Koha::Database->schema;
83
    my $row = $schema->resultset($source)->find($object_id);
84
    if ($row && $row->can('localizations')) {
85
        my $localization = $row->localizations->find($localization_id);
86
        if ($localization) {
87
            $localization->delete();
88
            Koha::Caches->get_instance('localization')->clear_from_cache("$source:" . $localization->lang);
89
        }
90
91
        $response->param(
92
            localization_id => $localization_id,
93
        );
94
    }
95
96
    C4::Service->return_success( $response );
96
    C4::Service->return_success( $response );
97
}
97
}
98
98
99
C4::Service->dispatch(
99
C4::Service->dispatch(
100
    [ 'GET /', [ 'id' ], \&get_translations ],
100
    [ 'PUT /', [], \&update_translation ],
101
    [ 'PUT /', [ 'id' ], \&update_translation ],
101
    [ 'POST /', [], \&add_translation ],
102
    [ 'POST /', [ 'entity', 'code', 'lang', 'translation' ], \&add_translation ],
102
    [ 'DELETE /', [],  \&delete_translation ],
103
    [ 'DELETE /', ['id'],  \&delete_translation ],
104
);
103
);
(-)a/t/db_dependent/Koha/ItemTypes.t (-12 / +12 lines)
Lines 26-31 use t::lib::TestBuilder; Link Here
26
26
27
use C4::Calendar qw( new );
27
use C4::Calendar qw( new );
28
use Koha::Biblioitems;
28
use Koha::Biblioitems;
29
use Koha::Caches;
29
use Koha::Libraries;
30
use Koha::Libraries;
30
use Koha::Database;
31
use Koha::Database;
31
use Koha::DateUtils qw(dt_from_string);;
32
use Koha::DateUtils qw(dt_from_string);;
Lines 66-95 my $child3 = $builder->build_object({ Link Here
66
        }
67
        }
67
    });
68
    });
68
69
69
Koha::Localization->new(
70
$child1->_result->localizations->create(
70
    {
71
    {
71
        entity      => 'itemtypes',
72
        property    => 'description',
72
        code        => $child1->itemtype,
73
        lang        => 'en',
73
        lang        => 'en',
74
        translation => 'b translated itemtype desc'
74
        translation => 'b translated itemtype desc'
75
    }
75
    }
76
)->store;
76
);
77
Koha::Localization->new(
77
$child2->_result->localizations->create(
78
    {
78
    {
79
        entity      => 'itemtypes',
79
        property    => 'description',
80
        code        => $child2->itemtype,
81
        lang        => 'en',
80
        lang        => 'en',
82
        translation => 'a translated itemtype desc'
81
        translation => 'a translated itemtype desc'
83
    }
82
    }
84
)->store;
83
);
85
Koha::Localization->new(
84
$child3->_result->localizations->create(
86
    {
85
    {
87
        entity      => 'something_else',
86
        property    => 'description',
88
        code        => $child2->itemtype,
89
        lang        => 'en',
87
        lang        => 'en',
90
        translation => 'another thing'
88
        translation => 'another thing'
91
    }
89
    }
92
)->store;
90
);
91
92
Koha::Caches->get_instance('localization')->flush_all();
93
93
94
my $type = Koha::ItemTypes->find($child1->itemtype);
94
my $type = Koha::ItemTypes->find($child1->itemtype);
95
ok( defined($type), 'first result' );
95
ok( defined($type), 'first result' );
(-)a/t/db_dependent/Koha/Template/Plugin/ItemTypes.t (-8 / +9 lines)
Lines 19-24 use Modern::Perl; Link Here
19
use Test::More tests => 10;
19
use Test::More tests => 10;
20
20
21
use C4::Context;
21
use C4::Context;
22
use Koha::Caches;
22
use Koha::Database;
23
use Koha::Database;
23
use Koha::ItemTypes;
24
use Koha::ItemTypes;
24
25
Lines 52-65 my $itemtypeA = $builder->build_object( Link Here
52
        }
53
        }
53
    }
54
    }
54
);
55
);
55
Koha::Localization->new(
56
$itemtypeA->_result->localizations->create(
56
    {
57
    {
57
        entity      => 'itemtypes',
58
        property    => 'description',
58
        code        => $itemtypeA->itemtype,
59
        lang        => 'en',
59
        lang        => 'en',
60
        translation => 'Translated itemtype A'
60
        translation => 'Translated itemtype A'
61
    }
61
    }
62
)->store;
62
);
63
my $itemtypeB = $builder->build_object(
63
my $itemtypeB = $builder->build_object(
64
    {
64
    {
65
        class  => 'Koha::ItemTypes',
65
        class  => 'Koha::ItemTypes',
Lines 69-82 my $itemtypeB = $builder->build_object( Link Here
69
        }
69
        }
70
    }
70
    }
71
);
71
);
72
Koha::Localization->new(
72
$itemtypeB->_result->localizations->create(
73
    {
73
    {
74
        entity      => 'itemtypes',
74
        property    => 'description',
75
        code        => $itemtypeB->itemtype,
76
        lang        => 'en',
75
        lang        => 'en',
77
        translation => 'Translated itemtype B'
76
        translation => 'Translated itemtype B'
78
    }
77
    }
79
)->store;
78
);
80
my $itemtypeC = $builder->build_object(
79
my $itemtypeC = $builder->build_object(
81
    {
80
    {
82
        class => 'Koha::ItemTypes',
81
        class => 'Koha::ItemTypes',
Lines 87-92 my $itemtypeC = $builder->build_object( Link Here
87
    }
86
    }
88
);
87
);
89
88
89
Koha::Caches->get_instance('localization')->flush_all();
90
90
my $GetDescriptionA1 = $plugin->GetDescription($itemtypeA->itemtype);
91
my $GetDescriptionA1 = $plugin->GetDescription($itemtypeA->itemtype);
91
is($GetDescriptionA1, "Translated itemtype A", "ItemType without parent - GetDescription without want parent");
92
is($GetDescriptionA1, "Translated itemtype A", "ItemType without parent - GetDescription without want parent");
92
my $GetDescriptionA2 = $plugin->GetDescription($itemtypeA->itemtype, 1);
93
my $GetDescriptionA2 = $plugin->GetDescription($itemtypeA->itemtype, 1);
(-)a/t/db_dependent/api/v1/item_types.t (-17 / +11 lines)
Lines 25-30 use t::lib::Mocks; Link Here
25
25
26
use Mojo::JSON qw(encode_json);
26
use Mojo::JSON qw(encode_json);
27
27
28
use Koha::Caches;
28
use Koha::ItemTypes;
29
use Koha::ItemTypes;
29
use Koha::Database;
30
use Koha::Database;
30
31
Lines 66-94 subtest 'list() tests' => sub { Link Here
66
        }
67
        }
67
    );
68
    );
68
69
69
    my $en = $builder->build_object(
70
    $item_type->_result->localizations->create(
70
        {
71
        {
71
            class => 'Koha::Localizations',
72
            property    => 'description',
72
            value => {
73
            lang        => 'en',
73
                entity      => 'itemtypes',
74
            translation => 'English word "test"',
74
                code        => $item_type->id,
75
                lang        => 'en',
76
                translation => 'English word "test"',
77
            }
78
        }
75
        }
79
    );
76
    );
80
    my $sv = $builder->build_object(
77
    $item_type->_result->localizations->create(
81
        {
78
        {
82
            class => 'Koha::Localizations',
79
            property    => 'description',
83
            value => {
80
            lang        => 'sv_SE',
84
                entity      => 'itemtypes',
81
            translation => 'Swedish word "test"',
85
                code        => $item_type->id,
86
                lang        => 'sv_SE',
87
                translation => 'Swedish word "test"',
88
            }
89
        }
82
        }
90
    );
83
    );
91
84
85
    Koha::Caches->get_instance('localization')->flush_all();
86
92
    my $librarian = $builder->build_object(
87
    my $librarian = $builder->build_object(
93
        {
88
        {
94
            class => 'Koha::Patrons',
89
            class => 'Koha::Patrons',
95
- 

Return to bug 38136