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

(-)a/C4/Biblio.pm (-10 / +99 lines)
Lines 63-68 BEGIN { Link Here
63
        DelBiblio
63
        DelBiblio
64
        BiblioAutoLink
64
        BiblioAutoLink
65
        LinkBibHeadingsToAuthorities
65
        LinkBibHeadingsToAuthorities
66
        ApplyMarcMergeRules
66
        TransformMarcToKoha
67
        TransformMarcToKoha
67
        TransformHtmlToMarc
68
        TransformHtmlToMarc
68
        TransformHtmlToXml
69
        TransformHtmlToXml
Lines 106-115 use Koha::ItemTypes; Link Here
106
use Koha::SearchEngine;
107
use Koha::SearchEngine;
107
use Koha::Libraries;
108
use Koha::Libraries;
108
use Koha::Util::MARC;
109
use Koha::Util::MARC;
110
use Koha::MarcMergeRules;
109
111
110
use vars qw($debug $cgi_debug);
112
use vars qw($debug $cgi_debug);
111
113
112
113
=head1 NAME
114
=head1 NAME
114
115
115
C4::Biblio - cataloging management functions
116
C4::Biblio - cataloging management functions
Lines 239-245 sub AddBiblio { Link Here
239
240
240
=head2 ModBiblio
241
=head2 ModBiblio
241
242
242
  ModBiblio( $record,$biblionumber,$frameworkcode, $disable_autolink);
243
  ModBiblio($record, $biblionumber, $frameworkcode, $options);
243
244
244
Replace an existing bib record identified by C<$biblionumber>
245
Replace an existing bib record identified by C<$biblionumber>
245
with one supplied by the MARC::Record object C<$record>.  The embedded
246
with one supplied by the MARC::Record object C<$record>.  The embedded
Lines 255-260 in the C<biblio> and C<biblioitems> tables, as well as Link Here
255
which fields are used to store embedded item, biblioitem,
256
which fields are used to store embedded item, biblioitem,
256
and biblionumber data for indexing.
257
and biblionumber data for indexing.
257
258
259
The C<$options> argument is a hashref with additional parameters:
260
261
=over 4
262
263
=item C<context>
264
265
This parameter is forwared to L<ApplyMarcMergeRules> where it is used for
266
selecting the current rule set if Marc Merge Rules is enabled.
267
See L<ApplyMarcMergeRules> for more details.
268
269
=item C<$disable_autolink>
270
258
Unless C<$disable_autolink> is passed ModBiblio will relink record headings
271
Unless C<$disable_autolink> is passed ModBiblio will relink record headings
259
to authorities based on settings in the system preferences. This flag allows
272
to authorities based on settings in the system preferences. This flag allows
260
us to not relink records when the authority linker is saving modifications.
273
us to not relink records when the authority linker is saving modifications.
Lines 264-270 Returns 1 on success 0 on failure Link Here
264
=cut
277
=cut
265
278
266
sub ModBiblio {
279
sub ModBiblio {
267
    my ( $record, $biblionumber, $frameworkcode, $disable_autolink ) = @_;
280
    my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
281
    $options //= {};
282
268
    if (!$record) {
283
    if (!$record) {
269
        carp 'No record passed to ModBiblio';
284
        carp 'No record passed to ModBiblio';
270
        return 0;
285
        return 0;
Lines 275-281 sub ModBiblio { Link Here
275
        logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
290
        logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
276
    }
291
    }
277
292
278
    if ( !$disable_autolink && C4::Context->preference('BiblioAddsAuthorities') ) {
293
    if ( !$options->{disable_autolink} && C4::Context->preference('BiblioAddsAuthorities') ) {
279
        BiblioAutoLink( $record, $frameworkcode );
294
        BiblioAutoLink( $record, $frameworkcode );
280
    }
295
    }
281
296
Lines 296-301 sub ModBiblio { Link Here
296
311
297
    _strip_item_fields($record, $frameworkcode);
312
    _strip_item_fields($record, $frameworkcode);
298
313
314
    # apply merge rules
315
    if (C4::Context->preference('MARCMergeRules') && $biblionumber && defined $options && exists $options->{'context'}) {
316
        $record = ApplyMarcMergeRules({
317
                biblionumber => $biblionumber,
318
                record => $record,
319
                context => $options->{'context'},
320
            }
321
        );
322
    }
323
299
    # update biblionumber and biblioitemnumber in MARC
324
    # update biblionumber and biblioitemnumber in MARC
300
    # FIXME - this is assuming a 1 to 1 relationship between
325
    # FIXME - this is assuming a 1 to 1 relationship between
301
    # biblios and biblioitems
326
    # biblios and biblioitems
Lines 306-318 sub ModBiblio { Link Here
306
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
331
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
307
332
308
    # load the koha-table data object
333
    # load the koha-table data object
309
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
334
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode, undef, $biblionumber);
310
335
311
    # update MARC subfield that stores biblioitems.cn_sort
336
    # update MARC subfield that stores biblioitems.cn_sort
312
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
337
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
313
338
314
    # update the MARC record (that now contains biblio and items) with the new record data
339
    # update the MARC record (that now contains biblio and items) with the new record data
315
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
340
    ModBiblioMarc( $record, $biblionumber, $frameworkcode );
316
341
317
    # modify the other koha tables
342
    # modify the other koha tables
318
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
343
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 2378-2384 sub TransformHtmlToMarc { Link Here
2378
2403
2379
=head2 TransformMarcToKoha
2404
=head2 TransformMarcToKoha
2380
2405
2381
    $result = TransformMarcToKoha( $record, undef, $limit )
2406
    $result = TransformMarcToKoha($record, undef, $biblionumber, $option)
2382
2407
2383
Extract data from a MARC bib record into a hashref representing
2408
Extract data from a MARC bib record into a hashref representing
2384
Koha biblio, biblioitems, and items fields.
2409
Koha biblio, biblioitems, and items fields.
Lines 2389-2395 hash_ref. Link Here
2389
=cut
2414
=cut
2390
2415
2391
sub TransformMarcToKoha {
2416
sub TransformMarcToKoha {
2392
    my ( $record, $frameworkcode, $limit_table ) = @_;
2417
    my ( $record, $frameworkcode, $limit_table, $biblionumber ) = @_;
2393
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2418
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2394
    $limit_table //= q{};
2419
    $limit_table //= q{};
2395
2420
Lines 3101-3107 sub _koha_delete_biblio_metadata { Link Here
3101
3126
3102
=head2 ModBiblioMarc
3127
=head2 ModBiblioMarc
3103
3128
3104
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3129
  ModBiblioMarc($newrec, $biblionumber, $frameworkcode);
3105
3130
3106
Add MARC XML data for a biblio to koha
3131
Add MARC XML data for a biblio to koha
3107
3132
Lines 3437-3444 sub RemoveAllNsb { Link Here
3437
    return $record;
3462
    return $record;
3438
}
3463
}
3439
3464
3440
1;
3465
=head2 ApplyMarcMergeRules
3466
3467
    my $record = ApplyMarcMergeRules($params)
3468
3469
Applies marc merge rules to a record.
3470
3471
C<$params> is expected to be a hashref with below keys defined.
3472
3473
=over 4
3441
3474
3475
=item C<biblionumber>
3476
biblionumber of old record
3477
3478
=item C<record>
3479
Incoming record that will be merged with old record
3480
3481
=item C<context>
3482
hashref containing at least one module from marc_merge_rules_modules table
3483
with filter value on the form {module => filter, ...}. Three predefined
3484
context modules exists:
3485
3486
    * source
3487
    * category
3488
    * borrower
3489
3490
=back
3491
3492
Returns:
3493
3494
=over 4
3495
3496
=item C<$record>
3497
3498
Merged MARC record based with merge rules for C<context> applied. If no old
3499
record for C<biblionumber> can be found, C<record> is returned unchanged.
3500
Default action when no matching context is found to return C<record> unchanged.
3501
If no rules are found for a certain field tag the default is to overwrite with
3502
fields with this field tag from C<record>.
3503
3504
=back
3505
3506
=cut
3507
3508
sub ApplyMarcMergeRules {
3509
    my ($params) = @_;
3510
    my $biblionumber = $params->{biblionumber};
3511
    my $incoming_record = $params->{record};
3512
3513
    if (!$biblionumber) {
3514
        carp 'ApplyMarcMergeRules called on undefined biblionumber';
3515
        return;
3516
    }
3517
    if (!$incoming_record) {
3518
        carp 'ApplyMarcMergeRules called on undefined record';
3519
        return;
3520
    }
3521
    my $old_record = GetMarcBiblio({ biblionumber => $biblionumber });
3522
3523
    # Skip merge rules if called with no context
3524
    if ($old_record && defined $params->{context}) {
3525
        return Koha::MarcMergeRules->merge_records($old_record, $incoming_record, $params->{context});
3526
    }
3527
    return $incoming_record;
3528
}
3529
3530
1;
3442
3531
3443
__END__
3532
__END__
3444
3533
(-)a/C4/ImportBatch.pm (-1 / +1 lines)
Lines 665-671 sub BatchCommitRecords { Link Here
665
                }
665
                }
666
                $oldxml = $old_marc->as_xml($marc_type);
666
                $oldxml = $old_marc->as_xml($marc_type);
667
667
668
                ModBiblio($marc_record, $recordid, $oldbiblio->frameworkcode);
668
                ModBiblio($marc_record, $recordid, $oldbiblio->frameworkcode, {context => {source => 'batchimport'}});
669
                $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
669
                $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
670
670
671
                if ($item_result eq 'create_new' || $item_result eq 'replace') {
671
                if ($item_result eq 'create_new' || $item_result eq 'replace') {
(-)a/Koha/Exceptions/MarcMergeRule.pm (+55 lines)
Line 0 Link Here
1
package Koha::Exceptions::MarcMergeRule;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use Exception::Class (
21
22
    'Koha::Exceptions::MarcMergeRule' => {
23
        description => 'Something went wrong!',
24
    },
25
    'Koha::Exceptions::MarcMergeRule::InvalidTagRegExp' => {
26
        isa => 'Koha::Exceptions::MarcMergeRule',
27
        description => 'Invalid regular expression for tag'
28
    },
29
    'Koha::Exceptions::MarcMergeRule::InvalidControlFieldActions' => {
30
        isa => 'Koha::Exceptions::MarcMergeRule',
31
        description => 'Invalid control field actions'
32
    }
33
);
34
35
=head1 NAME
36
37
Koha::Exceptions::MarcMergeRule - Base class for MarcMergeRule exceptions
38
39
=head1 Exceptions
40
41
=head2 Koha::Exceptions::MarcMergeRule
42
43
Generic MarcMergeRule exception
44
45
=head2 Koha::Exceptions::MarcMergeRule::InvalidTagRegExp
46
47
Exception for rule validation when rule tag is an invalid regular expression
48
49
=head2 Koha::Exceptions::MarcMergeRule::InvalidControlFieldActions
50
51
Exception for rule validation for control field rules with invalid combination of actions
52
53
=cut
54
55
1;
(-)a/Koha/MarcMergeRule.pm (+58 lines)
Line 0 Link Here
1
package Koha::MarcMergeRule;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use parent qw(Koha::Object);
21
22
my $cache = Koha::Caches->get_instance();
23
24
=head1 NAME
25
26
Koha::MarcMergeRule - Koha MarcMergeRule Object class
27
28
=cut
29
30
=head2 store
31
32
Override C<store> to clear marc merge rules cache.
33
34
=cut
35
36
sub store {
37
    my $self = shift @_;
38
    $cache->clear_from_cache('marc_merge_rules');
39
    $self->SUPER::store(@_);
40
}
41
42
=head2 delete
43
44
Override C<delete> to clear marc merge rules cache.
45
46
=cut
47
48
sub delete {
49
    my $self = shift @_;
50
    $cache->clear_from_cache('marc_merge_rules');
51
    $self->SUPER::delete(@_);
52
}
53
54
sub _type {
55
    return 'MarcMergeRule';
56
}
57
58
1;
(-)a/Koha/MarcMergeRules.pm (+382 lines)
Line 0 Link Here
1
package Koha::MarcMergeRules;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
use List::Util qw(first);
20
use Koha::MarcMergeRule;
21
use Carp;
22
23
use Koha::Exceptions::MarcMergeRule;
24
use Try::Tiny;
25
use Scalar::Util qw(looks_like_number);
26
27
use parent qw(Koha::Objects);
28
29
my $cache = Koha::Caches->get_instance();
30
31
=head1 NAME
32
33
Koha::MarcMergeRules - Koha MarcMergeRules Object set class
34
35
=head1 API
36
37
=head2 Class Methods
38
39
=head3 operations
40
41
Returns a list of all valid operations.
42
43
=cut
44
45
sub operations {
46
    return ('add', 'append', 'remove', 'delete');
47
}
48
49
=head3 context_rules
50
51
    my $rules = Koha::MarcMergeRules->context_rules($context);
52
53
Gets all MARC merge rules for the supplied C<$context> (hashref with { module => filter, ... } values).
54
55
=cut
56
57
sub context_rules {
58
    my ($self, $context) = @_;
59
60
    return unless %{$context};
61
62
    my $rules = $cache->get_from_cache('marc_merge_rules', { unsafe => 1 });
63
64
    if (!$rules) {
65
        $rules = {};
66
        my @rules_rows = $self->_resultset()->search(
67
            undef,
68
            {
69
                prefetch => 'module',
70
                order_by => { -desc => [qw/module.specificity me.id/] }
71
            }
72
        );
73
        foreach my $rule_row (@rules_rows) {
74
            my %rule = $rule_row->get_columns();
75
            my $operations = {};
76
77
            foreach my $operation ($self->operations) {
78
                $operations->{$operation} = { allow => $rule{$operation}, rule => $rule{id} };
79
            }
80
81
            # TODO: Remove unless check and validate on saving rules?
82
            if ($rule{tag} eq '*') {
83
                unless (exists $rules->{$rule{module}}->{$rule{filter}}->{'*'}) {
84
                    $rules->{$rule{module}}->{$rule{filter}}->{'*'} = $operations;
85
                }
86
            }
87
            elsif ($rule{tag} =~ /^(\d{3})$/) {
88
                unless (exists $rules->{$rule{module}}->{$rule{filter}}->{tags}->{$rule{tag}}) {
89
                    $rules->{$rule{module}}->{$rule{filter}}->{tags}->{$rule{tag}} = $operations;
90
                }
91
            }
92
            else {
93
                my $regexps = ($rules->{$rule{module}}->{$rule{filter}}->{regexps} //= []);
94
                push @{$regexps}, [$rule{tag}, $operations];
95
            }
96
        }
97
        $cache->set_in_cache('marc_merge_rules', $rules);
98
    }
99
100
    my $context_rules = undef;
101
    foreach my $module_name (keys %{$context}) {
102
        if (
103
            exists $rules->{$module_name} &&
104
            exists $rules->{$module_name}->{$context->{$module_name}}
105
        ) {
106
            $context_rules = $rules->{$module_name}->{$context->{$module_name}};
107
            last;
108
        }
109
    }
110
    if (!$context_rules) {
111
        # No perms matching specific context conditions found, try wildcard value for each active context
112
        foreach my $module_name (keys %{$context}) {
113
            if (exists $rules->{$module_name}->{'*'}) {
114
                $context_rules = $rules->{$module_name}->{'*'};
115
                last;
116
            }
117
        }
118
    }
119
    return $context_rules;
120
}
121
122
=head3 merge_records
123
124
    my $merged_record = Koha::MarcMergeRules->merge_records($old_record, $incoming_record, $context);
125
126
Merge C<$old_record> with C<$incoming_record> applying merge rules for C<$context>.
127
Returns merged record C<$merged_record>. C<$old_record>, C<$incoming_record> and
128
C<$merged_record> are all MARC::Record objects.
129
130
=cut
131
132
sub merge_records {
133
    my ($self, $old_record, $incoming_record, $context) = @_;
134
135
    my $rules = $self->context_rules($context);
136
137
    # Default when no rules found is to overwrite with incoming record
138
    return $incoming_record unless $rules;
139
140
    my $fields_by_tag = sub {
141
        my ($record) = @_;
142
        my $fields = {};
143
        foreach my $field ($record->fields()) {
144
            $fields->{$field->tag()} //= [];
145
            push @{$fields->{$field->tag()}}, $field;
146
        }
147
        return $fields;
148
    };
149
150
    my $hash_field_data = sub {
151
        my ($field) = @_;
152
        my $indicators = join("\x1E", map { $field->indicator($_) } (1, 2));
153
        return $indicators . "\x1E" . join("\x1E", sort map { join "\x1E", @{$_} } $field->subfields());
154
    };
155
156
    my $diff_by_key = sub {
157
        my ($a, $b) = @_;
158
        my @removed;
159
        my @intersecting;
160
        my @added;
161
        my %keys_index = map { $_ => undef } (keys %{$a}, keys %{$b});
162
        foreach my $key (keys %keys_index) {
163
            if ($a->{$key} && $b->{$key}) {
164
                push @intersecting, $a->{$key};
165
            }
166
            elsif ($a->{$key}) {
167
                push @removed, $a->{$key};
168
            }
169
            else {
170
                push @added, $b->{$key};
171
            }
172
        }
173
        return (\@removed, \@intersecting, \@added);
174
    };
175
176
    my $tag_rules = $rules->{tags} // {};
177
    my $default_rule = $rules->{'*'} // {
178
        add => { allow => 1, 'rule' => 0},
179
        append => { allow => 1, 'rule' => 0},
180
        delete => { allow => 1, 'rule' => 0},
181
        remove => { allow => 1, 'rule' => 0},
182
    };
183
184
    # Precompile regexps
185
    my @regexp_rules = map { { regexp => qr/^$_->[0]$/, actions => $_->[1] } } @{$rules->{regexps} // []};
186
187
    my $get_matching_field_rule = sub {
188
        my ($tag) = @_;
189
        # Exact match takes precedence, then regexp, then wildcard/defaults
190
        return $tag_rules->{$tag} //
191
            %{(first { $tag =~ $_->{regexp} } @regexp_rules) // {}}{actions} //
192
            $default_rule;
193
    };
194
195
    my %merged_record_fields;
196
197
    my $current_fields = $fields_by_tag->($old_record);
198
    my $incoming_fields = $fields_by_tag->($incoming_record);
199
200
    # First we get all new incoming fields
201
    my @new_field_tags = grep { !(exists $current_fields->{$_}) } keys %{$incoming_fields};
202
    foreach my $tag (@new_field_tags) {
203
        my $rule = $get_matching_field_rule->($tag);
204
        if ($rule->{add}->{allow}) {
205
            $merged_record_fields{$tag} //= [];
206
            push @{$merged_record_fields{$tag}}, @{$incoming_fields->{$tag}};
207
        }
208
    }
209
210
    # Then we get all fields no longer present in incoming fields
211
    my @deleted_field_tags = grep { !(exists $incoming_fields->{$_}) } keys %{$current_fields};
212
    foreach my $tag (@deleted_field_tags) {
213
        my $rule = $get_matching_field_rule->($tag);
214
        if (!$rule->{delete}->{allow}) {
215
            $merged_record_fields{$tag} //= [];
216
            push @{$merged_record_fields{$tag}}, @{$current_fields->{$tag}};
217
        }
218
    }
219
220
    # Then we get the intersection of fields, present both in
221
    # current and incoming record (possibly to be overwritten)
222
    my @common_field_tags = grep { exists $incoming_fields->{$_} } keys %{$current_fields};
223
    foreach my $tag (@common_field_tags) {
224
        my $rule = $get_matching_field_rule->($tag);
225
226
        # Special handling for control fields
227
        if ($tag < 10) {
228
            if (
229
                $rule->{append}->{allow} &&
230
                !$rule->{remove}->{allow}
231
            ) {
232
                # This should be highly unlikely since we have input validation to protect against this case
233
                carp "Allowing \"append\" and skipping \"remove\" is not permitted for control fields, falling back to skipping both \"append\" and \"remove\"";
234
                push @{$merged_record_fields{$tag}}, @{$current_fields->{$tag}};
235
            }
236
            elsif ($rule->{append}->{allow}) {
237
                push @{$merged_record_fields{$tag}}, @{$incoming_fields->{$tag}};
238
            }
239
            else {
240
                push @{$merged_record_fields{$tag}}, @{$current_fields->{$tag}};
241
            }
242
        }
243
        else {
244
            # Compute intersection and diff using field data
245
            my $sort_weight = 0;
246
            my %current_fields_by_data = map { $hash_field_data->($_) => [$sort_weight++, $_] } @{$current_fields->{$tag}};
247
248
            # Always put incoming fields after current fields
249
            my %incoming_fields_by_data = map { $hash_field_data->($_) => [$sort_weight++, $_] } @{$incoming_fields->{$tag}};
250
251
            my ($current_fields_only, $common_fields, $incoming_fields_only) = $diff_by_key->(\%current_fields_by_data, \%incoming_fields_by_data);
252
253
            my @merged_fields;
254
255
            # First add common fields (intersection)
256
            # Unchanged
257
            if (@{$common_fields}) {
258
                push @merged_fields, @{$common_fields};
259
            }
260
            # Removed
261
            if (@{$current_fields_only}) {
262
                if (!$rule->{remove}->{allow}) {
263
                    push @merged_fields, @{$current_fields_only};
264
                }
265
            }
266
            # Appended
267
            if (@{$incoming_fields_only}) {
268
                if ($rule->{append}->{allow}) {
269
                    push @merged_fields, @{$incoming_fields_only};
270
                }
271
            }
272
            $merged_record_fields{$tag} //= [];
273
274
            # Sort ascending according to weight (original order)
275
            push @{$merged_record_fields{$tag}}, map { $_->[1] } sort { $a->[0] <=> $b->[0] } @merged_fields;
276
        }
277
    }
278
279
    my $merged_record = MARC::Record->new();
280
281
    # Leader is always overwritten, or kept???
282
    $merged_record->leader($incoming_record->leader());
283
284
    if (%merged_record_fields) {
285
        foreach my $tag (sort keys %merged_record_fields) {
286
            $merged_record->append_fields(@{$merged_record_fields{$tag}});
287
        }
288
    }
289
    return $merged_record;
290
}
291
292
sub _clear_caches {
293
    $cache->clear_from_cache('marc_merge_rules');
294
}
295
296
=head2 find_or_create
297
298
Override C<find_or_create> to clear marc merge rules cache.
299
300
=cut
301
302
sub find_or_create {
303
    my $self = shift @_;
304
    $self->_clear_caches();
305
    $self->SUPER::find_or_create(@_);
306
}
307
308
=head2 update
309
310
Override C<update> to clear marc merge rules cache.
311
312
=cut
313
314
sub update {
315
    my $self = shift @_;
316
    $self->_clear_caches();
317
    return $self->SUPER::update(@_);
318
}
319
320
=head2 delete
321
322
Override C<delete> to clear marc merge rules cache.
323
324
=cut
325
326
sub delete {
327
    my $self = shift @_;
328
    $self->_clear_caches();
329
    return $self->SUPER::delete(@_);
330
}
331
332
=head2 validate
333
334
    Koha::MarcMergeRules->validate($rule_data);
335
336
Validates C<$rule_data>. Throws C<Koha::Exceptions::MarcMergeRule::InvalidTagRegExp>
337
if C<$rule_data->{tag}> contains an invalid regular expression. Throws
338
C<Koha::Exceptions::MarcMergeRule::InvalidControlFieldActions> if contains invalid
339
combination of actions for control fields. Otherwise returns true.
340
341
=cut
342
343
sub validate {
344
    my ($self, $rule_data) = @_;
345
346
    if(exists $rule_data->{tag}) {
347
        if ($rule_data->{tag} ne '*') {
348
            eval { qr/$rule_data->{tag}/ };
349
            if ($@) {
350
                Koha::Exceptions::MarcMergeRule::InvalidTagRegExp->throw(
351
                    "Invalid tag regular expression"
352
                );
353
            }
354
        }
355
        # TODO: Regexp or '*' that match controlfield not currently detected
356
        if (
357
            looks_like_number($rule_data->{tag}) &&
358
            $rule_data->{tag} < 10 &&
359
            $rule_data->{append} &&
360
            !$rule_data->{remove}
361
        ) {
362
            Koha::Exceptions::MarcMergeRule::InvalidControlFieldActions->throw(
363
                "Combination of allow append and skip remove not permitted for control fields"
364
            );
365
        }
366
    }
367
    return 1;
368
}
369
370
sub _type {
371
    return 'MarcMergeRule';
372
}
373
374
=head3 object_class
375
376
=cut
377
378
sub object_class {
379
    return 'Koha::MarcMergeRule';
380
}
381
382
1;
(-)a/admin/marc-merge-rules.pl (+160 lines)
Line 0 Link Here
1
#!/usr/bin/perl
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
# standard or CPAN modules used
21
use CGI qw ( -utf8 );
22
use CGI::Cookie;
23
use MARC::File::USMARC;
24
use Try::Tiny;
25
26
# Koha modules used
27
use C4::Context;
28
use C4::Koha;
29
use C4::Auth;
30
use C4::AuthoritiesMarc;
31
use C4::Output;
32
use C4::Biblio;
33
use C4::ImportBatch;
34
use C4::Matcher;
35
use C4::BackgroundJob;
36
use C4::Labels::Batch;
37
use Koha::MarcMergeRules;
38
use Koha::MarcMergeRule;
39
use Koha::Patron::Categories; # TODO: Required? Try without use
40
41
my $script_name = "/cgi-bin/koha/admin/marc-merge-rules.pl";
42
43
my $input = new CGI;
44
my $op = $input->param('op') || '';
45
my $errors = [];
46
47
my $rule_from_cgi = sub {
48
    my ($cgi) = @_;
49
50
    my %rule = map { $_ => scalar $cgi->param($_) } (
51
        'tag',
52
        'module',
53
        'filter',
54
        'add',
55
        'append',
56
        'remove',
57
        'delete'
58
    );
59
60
    my $id = $cgi->param('id');
61
    if ($id) {
62
        $rule{id} = $id;
63
    }
64
65
    return \%rule;
66
};
67
68
my ($template, $loggedinuser, $cookie) = get_template_and_user(
69
    {
70
        template_name   => "admin/marc-merge-rules.tt",
71
        query           => $input,
72
        type            => "intranet",
73
        authnotrequired => 0,
74
        flagsrequired   => { parameters => 'manage_marc_merge_rules' },
75
        debug           => 1,
76
    }
77
);
78
79
my %cookies = parse CGI::Cookie($cookie);
80
our $sessionID = $cookies{'CGISESSID'}->value;
81
82
my $get_rules = sub {
83
    # TODO: order?
84
    return [map { { $_->get_columns() } } Koha::MarcMergeRules->_resultset->all];
85
};
86
my $rules;
87
88
if ($op eq 'remove' || $op eq 'doremove') {
89
    my @remove_ids = $input->multi_param('batchremove');
90
    push @remove_ids, scalar $input->param('id') if $input->param('id');
91
    if ($op eq 'remove') {
92
        $template->{VARS}->{removeConfirm} = 1;
93
        my %remove_ids = map { $_ => undef } @remove_ids;
94
        $rules = $get_rules->();
95
        for my $rule (@{$rules}) {
96
            $rule->{'removemarked'} = 1 if exists $remove_ids{$rule->{id}};
97
        }
98
    }
99
    elsif ($op eq 'doremove') {
100
        my @remove_ids = $input->multi_param('batchremove');
101
        push @remove_ids, scalar $input->param('id') if $input->param('id');
102
        Koha::MarcMergeRules->search({ id => { in => \@remove_ids } })->delete();
103
        $rules = $get_rules->();
104
    }
105
}
106
elsif ($op eq 'edit') {
107
    $template->{VARS}->{edit} = 1;
108
    my $id = $input->param('id');
109
    $rules = $get_rules->();
110
    for my $rule(@{$rules}) {
111
        if ($rule->{id} == $id) {
112
            $rule->{'edit'} = 1;
113
            last;
114
        }
115
    }
116
}
117
elsif ($op eq 'doedit' || $op eq 'add') {
118
    my $rule_data = $rule_from_cgi->($input);
119
    if (!@{$errors}) {
120
        try {
121
            Koha::MarcMergeRules->validate($rule_data);
122
        }
123
        catch {
124
            die $_ unless blessed $_ && $_->can('rethrow');
125
126
            if ($_->isa('Koha::Exceptions::MarcMergeRule::InvalidTagRegExp')) {
127
                push @{$errors}, {
128
                    type => 'error',
129
                    code => 'invalid_tag_regexp',
130
                    tag => $rule_data->{tag},
131
                };
132
            }
133
            elsif ($_->isa('Koha::Exceptions::MarcMergeRule::InvalidControlFieldActions')) {
134
                push @{$errors}, {
135
                    type => 'error',
136
                    code => 'invalid_control_field_actions',
137
                    tag => $rule_data->{tag},
138
                };
139
            }
140
            else {
141
                $_->rethrow;
142
            }
143
        };
144
        if (!@{$errors}) {
145
            my $rule = Koha::MarcMergeRules->find_or_create($rule_data);
146
            # Need to call set and store here in case we have an update
147
            $rule->set($rule_data);
148
            $rule->store();
149
        }
150
        $rules = $get_rules->();
151
    }
152
}
153
else {
154
    $rules = $get_rules->();
155
}
156
157
my $categorycodes = Koha::Patron::Categories->search_limited({}, {order_by => ['description']});
158
$template->param( rules => $rules, categorycodes => $categorycodes, messages => $errors );
159
160
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/cataloguing/addbiblio.pl (-1 / +11 lines)
Lines 41-46 use Koha::ItemTypes; Link Here
41
use Koha::Libraries;
41
use Koha::Libraries;
42
42
43
use Koha::BiblioFrameworks;
43
use Koha::BiblioFrameworks;
44
use Koha::Patrons;
44
45
45
use Date::Calc qw(Today);
46
use Date::Calc qw(Today);
46
use MARC::File::USMARC;
47
use MARC::File::USMARC;
Lines 837-843 if ( $op eq "addbiblio" ) { Link Here
837
    if ( !$duplicatebiblionumber or $confirm_not_duplicate ) {
838
    if ( !$duplicatebiblionumber or $confirm_not_duplicate ) {
838
        my $oldbibitemnum;
839
        my $oldbibitemnum;
839
        if ( $is_a_modif ) {
840
        if ( $is_a_modif ) {
840
            ModBiblio( $record, $biblionumber, $frameworkcode );
841
            my $member = Koha::Patrons->find($loggedinuser);
842
            ModBiblio( $record, $biblionumber, $frameworkcode, {
843
                    context => {
844
                        source => $z3950 ? 'z39.50' : 'intranet',
845
                        categorycode => $member->categorycode,
846
                        borrower => $loggedinuser
847
                    }
848
                }
849
            );
841
        }
850
        }
842
        else {
851
        else {
843
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
852
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
Lines 930-935 elsif ( $op eq "delete" ) { Link Here
930
    $template->param(
939
    $template->param(
931
        biblionumberdata => $biblionumber,
940
        biblionumberdata => $biblionumber,
932
        op               => $op,
941
        op               => $op,
942
        z3950            => $z3950
933
    );
943
    );
934
    if ( $op eq "duplicate" ) {
944
    if ( $op eq "duplicate" ) {
935
        $biblionumber = "";
945
        $biblionumber = "";
(-)a/installer/data/mysql/atomicupdate/bug_14957-marc-merge-rules.perl (+41 lines)
Line 0 Link Here
1
$DBversion = 'XXX'; # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
    my $sql = q{
4
      CREATE TABLE IF NOT EXISTS `marc_merge_rules` (
5
        `id` int(11) NOT NULL auto_increment,
6
        `tag` varchar(255) NOT NULL,
7
        `module` varchar(127) NOT NULL,
8
        `filter` varchar(255) NOT NULL,
9
        `add` tinyint NOT NULL,
10
        `append` tinyint NOT NULL,
11
        `remove` tinyint NOT NULL,
12
        `delete` tinyint NOT NULL,
13
        PRIMARY KEY(`id`)
14
      );
15
    };
16
    $dbh->do( $sql );
17
18
    $sql = q{
19
      INSERT IGNORE INTO systempreferences (`variable`, `value`, `options`, `explanation`, `type`) VALUES (
20
        'MARCMergeRules',
21
        '0',
22
        NULL,
23
        'Use the MARC merge rules system to decide what actions to take for each field when modifying records.',
24
        'YesNo'
25
      );
26
    };
27
    $dbh->do( $sql );
28
29
    $sql = q{
30
      INSERT IGNORE INTO permissions (module_bit, code, description) VALUES (
31
        3,
32
        'manage_marc_merge_rules',
33
        'Manage MARC merge rules configuration'
34
      );
35
    };
36
    $dbh->do( $sql );
37
38
    # Always end with this (adjust the bug info)
39
    SetVersion( $DBversion );
40
    print "Upgrade to $DBversion done (Bug 14957 - Write protecting MARC fields based on source of import)\n";
41
}
(-)a/installer/data/mysql/kohastructure.sql (+30 lines)
Lines 1162-1167 CREATE TABLE `marc_matchers` ( Link Here
1162
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1162
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1163
1163
1164
--
1164
--
1165
-- Table structure for table `marc_merge_rules_modules`
1166
--
1167
1168
DROP TABLE IF EXISTS `marc_merge_rules_modules`;
1169
CREATE TABLE `marc_merge_rules_modules` (
1170
  `name` varchar(127) NOT NULL,
1171
  `description` varchar(255),
1172
  `specificity` int(11) NOT NULL UNIQUE,
1173
  PRIMARY KEY(`name`)
1174
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1175
1176
--
1177
-- Table structure for table `marc_merge_rules`
1178
--
1179
1180
DROP TABLE IF EXISTS `marc_merge_rules`;
1181
CREATE TABLE IF NOT EXISTS `marc_merge_rules` (
1182
  `id` int(11) NOT NULL auto_increment,
1183
  `tag` varchar(255) NOT NULL, -- can be regexp, so need > 3 chars
1184
  `module` varchar(127) NOT NULL,
1185
  `filter` varchar(255) NOT NULL,
1186
  `add` tinyint NOT NULL,
1187
  `append` tinyint NOT NULL,
1188
  `remove` tinyint NOT NULL,
1189
  `delete` tinyint NOT NULL,
1190
  PRIMARY KEY(`id`),
1191
  CONSTRAINT `marc_merge_rules_ibfk1` FOREIGN KEY (`module`) REFERENCES `marc_merge_rules_modules` (`name`) ON DELETE CASCADE ON UPDATE CASCADE
1192
);
1193
1194
--
1165
-- Table structure for table `matchpoints`
1195
-- Table structure for table `matchpoints`
1166
--
1196
--
1167
DROP TABLE IF EXISTS `matchpoints`;
1197
DROP TABLE IF EXISTS `matchpoints`;
(-)a/installer/data/mysql/sysprefs.sql (+1 lines)
Lines 295-300 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
295
('MarcFieldForModifierName','',NULL,'Where to store the name of the record''s last modifier','Free'),
295
('MarcFieldForModifierName','',NULL,'Where to store the name of the record''s last modifier','Free'),
296
('MarcFieldsToOrder','',NULL,'Set the mapping values for a new order line created from a MARC record in a staged file. In a YAML format.','textarea'),
296
('MarcFieldsToOrder','',NULL,'Set the mapping values for a new order line created from a MARC record in a staged file. In a YAML format.','textarea'),
297
('MarcItemFieldsToOrder','',NULL,'Set the mapping values for new item records created from a MARC record in a staged file. In a YAML format.','textarea'),
297
('MarcItemFieldsToOrder','',NULL,'Set the mapping values for new item records created from a MARC record in a staged file. In a YAML format.','textarea'),
298
('MARCMergeRules','0',NULL,'Use the MARC merge rules system to decide what actions to take for each field when modifying records.','YesNo'),
298
('MarkLostItemsAsReturned','batchmod,moredetail,cronjob,additem,pendingreserves','batchmod|moredetail|cronjob|additem|pendingreserves','Mark items as returned when flagged as lost','multiple'),
299
('MarkLostItemsAsReturned','batchmod,moredetail,cronjob,additem,pendingreserves','batchmod|moredetail|cronjob|additem|pendingreserves','Mark items as returned when flagged as lost','multiple'),
299
('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'),
300
('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'),
300
('MaxFine',NULL,'','Maximum fine a patron can have for all late returns at one moment. Single item caps are specified in the circulation rules matrix.','Integer'),
301
('MaxFine',NULL,'','Maximum fine a patron can have for all late returns at one moment. Single item caps are specified in the circulation rules matrix.','Integer'),
(-)a/installer/data/mysql/userpermissions.sql (+1 lines)
Lines 24-29 INSERT INTO permissions (module_bit, code, description) VALUES Link Here
24
   ( 3, 'manage_oai_sets', 'Manage OAI sets'),
24
   ( 3, 'manage_oai_sets', 'Manage OAI sets'),
25
   ( 3, 'manage_item_search_fields', 'Manage item search fields'),
25
   ( 3, 'manage_item_search_fields', 'Manage item search fields'),
26
   ( 3, 'manage_search_engine_config', 'Manage search engine configuration'),
26
   ( 3, 'manage_search_engine_config', 'Manage search engine configuration'),
27
   ( 3, 'manage_marc_merge_rules', 'Manage MARC merge rules configuration'),
27
   ( 3, 'manage_search_targets', 'Manage Z39.50 and SRU server configuration'),
28
   ( 3, 'manage_search_targets', 'Manage Z39.50 and SRU server configuration'),
28
   ( 3, 'manage_didyoumean', 'Manage Did you mean? configuration'),
29
   ( 3, 'manage_didyoumean', 'Manage Did you mean? configuration'),
29
   ( 3, 'manage_column_config', 'Manage column configuration'),
30
   ( 3, 'manage_column_config', 'Manage column configuration'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc (+3 lines)
Lines 80-85 Link Here
80
            [% IF ( Koha.Preference('SearchEngine') == 'Elasticsearch' && CAN_user_parameters_manage_search_engine_config ) %]
80
            [% IF ( Koha.Preference('SearchEngine') == 'Elasticsearch' && CAN_user_parameters_manage_search_engine_config ) %]
81
                <li><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></li>
81
                <li><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></li>
82
            [% END %]
82
            [% END %]
83
            [% IF ( CAN_user_parameters_manage_marc_merge_rules ) %]
84
                <li><a href="/cgi-bin/koha/admin/marc-merge-rules.pl">MARC merge rules</a></li>
85
            [% END %]
83
        </ul>
86
        </ul>
84
    [% END %]
87
    [% END %]
85
88
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc (+5 lines)
Lines 201-206 Link Here
201
            Manage search engine configuration
201
            Manage search engine configuration
202
        </span>
202
        </span>
203
        <span class="permissioncode">([% name | html %])</span>
203
        <span class="permissioncode">([% name | html %])</span>
204
    [%- CASE 'manage_marc_merge_rules' -%]
205
        <span class="sub_permission manage_marc_merge_rules_subpermission">
206
          Manage MARC merge rules configuration
207
        </span>
208
        <span class="permissioncode">([% name | html %])</span>
204
    [%- CASE 'manage_search_targets' -%]
209
    [%- CASE 'manage_search_targets' -%]
205
        <span class="sub_permission manage_search_targets_subpermission">
210
        <span class="sub_permission manage_search_targets_subpermission">
206
            Manage Z39.50 and SRU server configuration
211
            Manage Z39.50 and SRU server configuration
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+2 lines)
Lines 147-152 Link Here
147
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt>
147
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt>
148
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd>
148
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd>
149
                    [% END %]
149
                    [% END %]
150
                    <dt><a href="/cgi-bin/koha/admin/marc-merge-rules.pl">MARC merge rules</a></dt>
151
                    <dd>Managed MARC field merge rules</dd>
150
                </dl>
152
                </dl>
151
            [% END %]
153
            [% END %]
152
154
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-merge-rules.tt (+481 lines)
Line 0 Link Here
1
[% USE Koha %]
2
[% INCLUDE 'doc-head-open.inc' %]
3
<title>Koha &rsaquo; Administration &rsaquo; MARC merge rules</title>
4
[% INCLUDE 'doc-head-close.inc' %]
5
[% Asset.css("css/datatables.css") %]
6
[% INCLUDE 'datatables.inc' %]
7
8
<style type="text/css">
9
    .required {
10
        background-color: #C00;
11
    }
12
</style>
13
14
<script type="text/javascript">
15
function doSubmit(op, id) {
16
    $('<input type="hidden"/>')
17
    .attr('name', 'op')
18
    .attr('value', op)
19
    .appendTo('#marc-merge-rules-form');
20
21
    if(id) {
22
        $('<input type="hidden"/>')
23
        .attr('name', 'id')
24
        .attr('value', id)
25
        .appendTo('#marc-merge-rules-form');
26
    }
27
28
    var valid = true;
29
    if( op == 'add' || op == 'edit') {
30
        var validate = [
31
                        $('#marc-merge-rules-form input[name="filter"]'),
32
                        $('#marc-merge-rules-form input[name="tag"]')
33
                       ];
34
        for(var i=0; i < validate.length; i++) {
35
            if(validate[i].val().length == 0) {
36
                validate[i].addClass('required');
37
                valid = false;
38
            } else {
39
                validate[i].removeClass('required');
40
            }
41
        }
42
    }
43
44
    if(valid ) {
45
        $('#marc-merge-rules-form').submit();
46
    }
47
48
    return valid;
49
}
50
51
$(document).ready(function(){
52
    $('#doremove').on("click",function(){
53
        doSubmit('doremove');
54
    });
55
    $('#doedit').on("click",function(){
56
        doSubmit('doedit', $("#doedit").attr('value'));
57
    });
58
    $('#add').on("click", function(){
59
        doSubmit('add');
60
        return false;
61
    });
62
    $('#btn_batchremove').on("click", function(){
63
        doSubmit('remove');
64
    });
65
66
    /* Disable batch remove unless one or more checkboxes are checked */
67
    $('input[name="batchremove"]').change(function() {
68
        if($('input[name="batchremove"]:checked').length > 0) {
69
            $('#btn_batchremove').removeAttr('disabled');
70
        } else {
71
            $('#btn_batchremove').attr('disabled', 'disabled');
72
        }
73
    });
74
75
    $.fn.dataTable.ext.order['dom-input'] = function (settings, col) {
76
        return this.api().column(col, { order: 'index' }).nodes()
77
            .map(function (td, i) {
78
                if($('input', td).val() != undefined) {
79
                    return $('input', td).val();
80
                } else if($('select', td).val() != undefined) {
81
                    return $('option[selected="selected"]', td).val();
82
                } else {
83
                    return $(td).html();
84
                }
85
            });
86
    }
87
88
    $('#marc-merge-rules').dataTable($.extend(true, {}, dataTablesDefaults, {
89
        "aoColumns": [
90
            {"bSearchable": false, "bSortable": false},
91
            {"sSortDataType": "dom-input"},
92
            {"sSortDataType": "dom-input"},
93
            {"bSearchable": false, "sSortDataType": "dom-input"},
94
            {"bSearchable": false, "sSortDataType": "dom-input"},
95
            {"bSearchable": false, "sSortDataType": "dom-input"},
96
            {"bSearchable": false, "sSortDataType": "dom-input"},
97
            {"bSearchable": false, "sSortDataType": "dom-input"},
98
            {"bSearchable": false, "sSortDataType": "dom-input"},
99
            {"bSearchable": false, "bSortable": false},
100
            {"bSearchable": false, "bSortable": false}
101
        ],
102
        "sPaginationType": "four_button"
103
    }));
104
105
    var merge_rules_presets = {
106
      'Protect': {
107
        'add': 0,
108
        'append': 0,
109
        'remove': 0,
110
        'delete': 0
111
      },
112
      'Overwrite': {
113
        'add': 1,
114
        'append': 1,
115
        'remove': 1,
116
        'delete': 1
117
      },
118
      'Add new': {
119
        'add': 1,
120
        'append': 0,
121
        'remove': 0,
122
        'delete': 0
123
      },
124
      'Add and append': {
125
        'add': 1,
126
        'append': 1,
127
        'remove': 0,
128
        'delete': 0
129
      },
130
      'Protect from deletion': {
131
        'add': 1,
132
        'append': 1,
133
        'remove': 1,
134
        'delete': 0
135
      },
136
    };
137
138
    var merge_rules_label_to_value = {
139
      'Add': 1,
140
      'Append': 1,
141
      'Remove': 1,
142
      'Delete': 1,
143
      'Skip': 0
144
    };
145
146
    var merge_rules_preset_map = {};
147
    $.each(merge_rules_presets, function(preset, config) {
148
      merge_rules_preset_map[JSON.stringify(config)] = preset;
149
    });
150
151
    function operations_config_merge_rule_preset(config) {
152
      return merge_rules_preset_map[JSON.stringify(config)] || '';
153
    }
154
155
    /* Set preset values according to operation config */
156
    $('.rule').each(function() {
157
      var $this = $(this);
158
      var operations_config = {};
159
      $('.rule-operation-action', $this).each(function() {
160
        var $operation = $(this);
161
        operations_config[$operation.data('operation')] = merge_rules_label_to_value[$operation.text()];
162
      });
163
      $('.rule-preset', $this).text(
164
        operations_config_merge_rule_preset(operations_config)
165
      );
166
    });
167
168
    /* Listen to operations config changes and set presets accordingly */
169
    $('.rule-operation-action-edit select').change(function() {
170
      var operations_config = {};
171
      var $parent_row = $(this).closest('tr');
172
      $('.rule-operation-action-edit select', $parent_row).each(function() {
173
        var $this = $(this);
174
        operations_config[$this.attr('name')] = $this.val();
175
      });
176
      $('select[name="preset"]', $parent_row).val(
177
          operations_config_merge_rule_preset(operations_config)
178
      );
179
    });
180
181
    /* Listen to preset changes and set operations config accordingly */
182
    $('select[name="preset"]').change(function() {
183
      var $this = $(this);
184
      var $parent_row = $this.closest('tr');
185
      var preset = $this.val();
186
      if (preset) {
187
        $.each(merge_rules_presets[preset], function(operation, action) {
188
          $('select[name="' + operation + '"]', $parent_row).val(action);
189
        });
190
      }
191
    });
192
193
    var module_filter_options = {
194
      source: {
195
        '*': '*',
196
        batchmod: "Batch record modification",
197
        intranet: "Staff client MARC editor",
198
        batchimport: "Staged MARC import",
199
        z3950: "Z39.50",
200
        bulkmarcimport: "bulkmarcimport.pl",
201
        import_lexile: "import_lexile.pl"
202
      },
203
      categorycode: {
204
        '*': '*',
205
        [% FOREACH categorycode IN categorycodes %]
206
          [% categorycode.categorycode | html %]: "[% categorycode.description | html %]",
207
        [% END %]
208
      }
209
    };
210
211
    var $filter_container = $('#filter-container');
212
213
    /* Listen to module changes and set filter input accordingly */
214
    $('select[name="module"]').change(function() {
215
      var $this = $(this);
216
      var module_name = $this.val();
217
218
      /* Remove current element if any */
219
      $filter_container.empty();
220
221
      var filter_elem = null;
222
      if (module_name in module_filter_options) {
223
        // Create select element
224
        filter_elem = document.createElement('select');
225
        for (var filter_value in module_filter_options[module_name]) {
226
          var option = document.createElement('option');
227
          option.value = filter_value;
228
          option.text = module_filter_options[module_name][filter_value];
229
          filter_elem.appendChild(option);
230
        }
231
      }
232
      else {
233
        // Create text input element
234
        filter_elem = document.createElement('input');
235
        filter_elem.type = 'text';
236
        filter_elem.setAttribute('size', 5);
237
      }
238
      filter_elem.name = 'filter';
239
      filter_elem.id = 'filter';
240
      $filter_container.append(filter_elem);
241
    }).change(); // Trigger change
242
243
    // Hack: set value if editing rule
244
    if ($filter_container.data('filter')) {
245
      $('#filter').val($filter_container.data('filter'));
246
    }
247
});
248
</script>
249
</head>
250
<body id="admin_marc-merge-rules" class="admin">
251
[% INCLUDE 'header.inc' %]
252
[% INCLUDE 'cat-search.inc' %]
253
254
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a>
255
 &rsaquo; MARC merge rules
256
</div>
257
258
<div id="doc3" class="yui-t2">
259
   <div id="bd">
260
    <div id="yui-main">
261
    <div class="yui-b">
262
263
<h1>Manage MARC merge rules</h1>
264
265
[% FOR m IN messages %]
266
  <div class="dialog [% m.type | html %]">
267
    [% SWITCH m.code %]
268
    [% CASE 'invalid_tag_regexp' %]
269
      Invalid regular expression "[% m.tag | html %]".
270
    [% CASE 'invalid_control_field_actions' %]
271
      Invalid combination of actions for tag [% m.tag | html %]. Control field rules do not allow "Appended: Append" and "Removed: Skip".
272
    [% CASE %]
273
      [% m.code | html %]
274
    [% END %]
275
  </div>
276
[% END %]
277
278
[% UNLESS Koha.Preference( 'MARCMergeRules' ) %]
279
    <div class="dialog message">
280
        The <b>MARCMergeRules</b> preference is not set, don't forget to enable it for rules to take effect.
281
    </div>
282
[% END %]
283
[% IF removeConfirm %]
284
<div class="dialog alert">
285
<h3>Remove rule?</h3>
286
<p>Are you sure you want to remove the selected rule(s)?</p>
287
288
<form action="[% script_name %]" method="GET">
289
    <input type="submit" value="No, do not remove" class="deny"/>
290
</form>
291
<input type="button" value="Yes, remove" class="approve" id="doremove" />
292
</div>
293
[% END %]
294
295
<form action="[% script_name %]" method="POST" id="marc-merge-rules-form">
296
<table id="marc-merge-rules">
297
    <thead><tr>
298
        <th>Rule</th>
299
        <th>Module</th>
300
        <th>Filter</th>
301
        <th>Tag</th>
302
        <th>Preset</th>
303
        <th>Added</th>
304
        <th>Appended</th>
305
        <th>Removed</th>
306
        <th>Deleted</th>
307
        <th>Actions</th>
308
        <th>&nbsp;</th>
309
    </tr></thead>
310
    [% UNLESS edit %]
311
    <tfoot>
312
        <tr class="rule-new">
313
            <th>&nbsp;</th>
314
            <th>
315
                <select name="module">
316
                    <option value="source">Source</option>
317
                    <option value="categorycode">User category</option>
318
                    <option value="userid">Username</option>
319
                </select>
320
            </th>
321
            <th id="filter-container"></th>
322
            <th><input type="text" size="5" name="tag"/></th>
323
            <th>
324
                <select name="preset">
325
                    <option value="" selected>Custom</option>
326
                    [% FOR preset IN ['Protect', 'Overwrite', 'Add new', 'Add and append', 'Protect from deletion'] %]
327
                        <option value="[% preset %]">[% preset %]</option>
328
                    [% END %]
329
                </select>
330
            </th>
331
            <th class="rule-operation-action-edit">
332
                <select name="add">
333
                    <option value="0">Skip</option>
334
                    <option value="1">Add</option>
335
                </select>
336
            </th>
337
            <th class="rule-operation-action-edit">
338
                <select name="append">
339
                    <option value="0">Skip</option>
340
                    <option value="1">Append</option>
341
                </select>
342
            </th>
343
            <th class="rule-operation-action-edit">
344
                <select name="remove">
345
                    <option value="0">Skip</option>
346
                    <option value="1">Remove</option>
347
                </select>
348
            </th>
349
            <th class="rule-operation-action-edit">
350
                <select name="delete">
351
                    <option value="0">Skip</option>
352
                    <option value="1">Delete</option>
353
                </select>
354
            </th>
355
            <th><button class="btn btn-sm" title="Add" id="add"><i class="fa fa-plus"></i> Add rule</button></th>
356
            <th><button id="btn_batchremove" disabled="disabled" class="btn btn-sm" title="Batch remove"><i class="fa fa-trash"></i> Delete selected</button></th>
357
        </tr>
358
    </tfoot>
359
    [% END %]
360
    <tbody>
361
        [% FOREACH rule IN rules %]
362
            <tr id="[% rule.id %]" class="rule[% IF rule.edit %]-edit[% END %]">
363
            [% IF rule.edit %]
364
                <td>[% rule.id %]</td>
365
                <td>
366
                    <select name="module">
367
                        [% IF rule.module == "source" %]
368
                            <option value="source" selected="selected">Source</option>
369
                        [% ELSE %]
370
                            <option value="source">Source</option>
371
                        [% END %]
372
                        [% IF rule.module == "categorycode" %]
373
                            <option value="categorycode" selected="selected">User category</option>
374
                        [% ELSE %]
375
                            <option value="categorycode">User category</option>
376
                        [% END %]
377
                        [% IF rule.module == "userid" %]
378
                            <option value="userid" selected="selected">Username</option>
379
                        [% ELSE %]
380
                            <option value="userid">Username</option>
381
                        [% END %]
382
                    </select>
383
                </td>
384
                <td id="filter-container" data-filter="[% rule.filter | html %]"></td>
385
                <td><input type="text" size="3" name="tag" value="[% rule.tag | html %]"/></td>
386
                <th>
387
                    <select name="preset">
388
                        <option value="" selected>Custom</option>
389
                        [% FOR preset IN ['Protect', 'Overwrite', 'Add new', 'Add and append', 'Protect from deletion'] %]
390
                            <option value="[% preset %]">[% preset %]</option>
391
                        [% END %]
392
                    </select>
393
                </th>
394
                <td class="rule-operation-action-edit">
395
                    <select name="add">
396
                        [% IF rule.add %]
397
                            <option value="0">Skip</option>
398
                            <option value="1" selected="selected">Add</option>
399
                        [% ELSE %]
400
                            <option value="0" selected="selected">Skip</option>
401
                            <option value="1">Add</option>
402
                        [% END %]
403
                    </select>
404
                </td>
405
                <td class="rule-operation-action-edit">
406
                    <select name="append">
407
                        [% IF rule.append %]
408
                            <option value="0">Skip</option>
409
                            <option value="1" selected="selected">Append</option>
410
                        [% ELSE %]
411
                            <option value="0" selected="selected">Skip</option>
412
                            <option value="1">Append</option>
413
                        [% END %]
414
                    </select>
415
                </td>
416
                <td class="rule-operation-action-edit">
417
                    <select name="remove">
418
                        [% IF rule.remove %]
419
                            <option value="0">Skip</option>
420
                            <option value="1" selected="selected">Remove</option>
421
                        [% ELSE %]
422
                            <option value="0" selected="selected">Skip</option>
423
                            <option value="1">Remove</option>
424
                        [% END %]
425
                    </select>
426
                </td>
427
                <td class="rule-operation-action-edit">
428
                    <select name="delete">
429
                        [% IF rule.delete %]
430
                            <option value="0">Skip</option>
431
                            <option value="1" selected="selected">Delete</option>
432
                        [% ELSE %]
433
                            <option value="0" selected="selected">Skip</option>
434
                            <option value="1">Delete</option>
435
                        [% END %]
436
                    </select>
437
                </td>
438
                <td class="actions">
439
                    <button class="btn btn-sm" title="Save" id="doedit" value="[% rule.id | html %]"><i class="fa fa-check"></i> Save</button>
440
                    <a href="?"><button class="btn btn-sm" title="Cancel" ><i class="fa fa-times"></i> Cancel</button></a>
441
                </td>
442
                <td></td>
443
            [% ELSE %]
444
                <td>[% rule.id | html %]</td>
445
                <td>[% rule.module | html %]</td>
446
                <td>[% rule.filter | html %]</td>
447
                <td>[% rule.tag | html %]</td>
448
                <td class="rule-preset"></td>
449
                <td class="rule-operation-action" data-operation="add">[% IF rule.add %]Add[% ELSE %]Skip[% END %]</td>
450
                <td class="rule-operation-action" data-operation="append">[% IF rule.append %]Append[% ELSE %]Skip[% END %]</td>
451
                <td class="rule-operation-action" data-operation="remove">[% IF rule.remove %]Remove[% ELSE %]Skip[% END %]</td>
452
                <td class="rule-operation-action" data-operation="delete">[% IF rule.delete %]Delete[% ELSE %]Skip[% END %]</td>
453
                <td class="actions">
454
                    <a href="?op=remove&id=[% rule.id %]" title="Delete" class="btn btn-sm"><i class="fa fa-trash"></i> Delete</a>
455
                    <a href="?op=edit&id=[% rule.id %]" title="Edit" class="btn btn-sm"><i class="fa fa-pencil"></i> Edit</a>
456
                </td>
457
                <td>
458
                    [% IF rule.removemarked %]
459
                        <input type="checkbox" name="batchremove" value="[% rule.id | html %]" checked="checked"/>
460
                    [% ELSE %]
461
                        <input type="checkbox" name="batchremove" value="[% rule.id | html %]"/>
462
                    [% END %]
463
                </td>
464
            [% END %]
465
            </tr>
466
        [% END %]
467
    </tbody>
468
</table>
469
</form>
470
471
<form action="[% script_name %]" method="post">
472
<input type="hidden" name="op" value="redo-matching" />
473
</form>
474
475
</div>
476
</div>
477
<div class="yui-b">
478
[% INCLUDE 'admin-menu.inc' %]
479
</div>
480
</div>
481
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref (+7 lines)
Lines 294-296 Cataloging: Link Here
294
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
294
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
295
            - "<br/>"
295
            - "<br/>"
296
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
296
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
297
        -
298
            - When importing records
299
            - pref: MARCMergeRules
300
              choices:
301
                  yes: "use"
302
                  no: "don't use"
303
            - MARC merge rules to decide which action to take for each field.
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt (+1 lines)
Lines 611-616 function Changefwk() { Link Here
611
[% END %]
611
[% END %]
612
        <input type="hidden" name="op" value="addbiblio" />
612
        <input type="hidden" name="op" value="addbiblio" />
613
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode | html %]" />
613
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode | html %]" />
614
        <input type="hidden" name="z3950" value="[% z3950 | html %]" />
614
        <input type="hidden" name="biblionumber" value="[% biblionumber | html %]" />
615
        <input type="hidden" name="biblionumber" value="[% biblionumber | html %]" />
615
        <input type="hidden" name="breedingid" value="[% breedingid | html %]" />
616
        <input type="hidden" name="breedingid" value="[% breedingid | html %]" />
616
        <input type="hidden" name="changed_framework" value="" />
617
        <input type="hidden" name="changed_framework" value="" />
(-)a/misc/link_bibs_to_authorities.pl (-1 / +1 lines)
Lines 221-227 sub process_bib { Link Here
221
            );
221
            );
222
        }
222
        }
223
        if ( not $test_only ) {
223
        if ( not $test_only ) {
224
            ModBiblio( $bib, $biblionumber, $frameworkcode, 1 );
224
            ModBiblio( $bib, $biblionumber, $frameworkcode, { disable_autolink => 1 });
225
            #Last param is to note ModBiblio was called from linking script and bib should not be linked again
225
            #Last param is to note ModBiblio was called from linking script and bib should not be linked again
226
            $num_bibs_modified++;
226
            $num_bibs_modified++;
227
        }
227
        }
(-)a/misc/migration_tools/bulkmarcimport.pl (-1 / +1 lines)
Lines 458-464 RECORD: while ( ) { Link Here
458
                    $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
458
                    $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
459
                };
459
                };
460
                if ($update) {
460
                if ($update) {
461
                    eval { ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
461
                    eval { ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber), {context => {source => 'bulkmarcimport'}} ) };
462
                    if ($@) {
462
                    if ($@) {
463
                        warn "ERROR: Edit biblio $biblionumber failed: $@\n";
463
                        warn "ERROR: Edit biblio $biblionumber failed: $@\n";
464
                        printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
464
                        printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
(-)a/misc/migration_tools/import_lexile.pl (-1 / +2 lines)
Lines 154-159 while ( my $row = $csv->getline_hr($fh) ) { Link Here
154
    foreach my $biblionumber (@biblionumbers) {
154
    foreach my $biblionumber (@biblionumbers) {
155
        $counter++;
155
        $counter++;
156
        my $record = GetMarcBiblio({ biblionumber => $biblionumber });
156
        my $record = GetMarcBiblio({ biblionumber => $biblionumber });
157
        my $frameworkcode = GetFrameworkCode($biblionumber);
157
158
158
        if ($verbose) {
159
        if ($verbose) {
159
            say "Found matching record! Biblionumber: $biblionumber";
160
            say "Found matching record! Biblionumber: $biblionumber";
Lines 203-209 while ( my $row = $csv->getline_hr($fh) ) { Link Here
203
            $record->append_fields($field);
204
            $record->append_fields($field);
204
        }
205
        }
205
206
206
        ModBiblio( $record, $biblionumber ) unless ( $test );
207
        ModBiblio( $record, $biblionumber, $frameworkcode, {context => {source => 'import_lexile'}} ) unless ( $test );
207
    }
208
    }
208
209
209
}
210
}
(-)a/t/db_dependent/Biblio.t (-1 / +1 lines)
Lines 583-589 subtest 'ModBiblio called from linker test' => sub { Link Here
583
    C4::Biblio::ModBiblio($record,$biblionumber,'');
583
    C4::Biblio::ModBiblio($record,$biblionumber,'');
584
    is($called,1,"We called to link bibs because not from linker");
584
    is($called,1,"We called to link bibs because not from linker");
585
    $called = 0;
585
    $called = 0;
586
    C4::Biblio::ModBiblio($record,$biblionumber,'',1);
586
    C4::Biblio::ModBiblio($record,$biblionumber,'',{ disable_autolink => 1 });
587
    is($called,0,"We didn't call to link bibs because from linker");
587
    is($called,0,"We didn't call to link bibs because from linker");
588
};
588
};
589
589
(-)a/t/db_dependent/Biblio/MarcMergeRules.t (+784 lines)
Line 0 Link Here
1
#!/usr/bin/perl
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
use Try::Tiny;
20
21
use MARC::Record;
22
23
use C4::Context;
24
use C4::Biblio;
25
use Koha::Database; #??
26
27
use Test::More tests => 23;
28
use Test::MockModule;
29
30
use Koha::MarcMergeRules;
31
use Koha::MarcMergeRulesModules;
32
33
use t::lib::Mocks;
34
35
my $schema = Koha::Database->schema;
36
$schema->storage->txn_begin;
37
38
t::lib::Mocks::mock_preference('MARCMergeRules', '1');
39
40
# Create a record
41
my $orig_record = MARC::Record->new();
42
$orig_record->append_fields (
43
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
44
    MARC::Field->new('250', '','', 'a' => '256 bottles of beer on the wall'),
45
    MARC::Field->new('500', '','', 'a' => 'One bottle of beer in the fridge'),
46
);
47
48
my $modules_rs = Koha::MarcMergeRulesModules->search(undef, { order_by => { -desc => 'specificity' } });
49
my @modules;
50
push @modules, $modules_rs->next;
51
52
my $incoming_record = MARC::Record->new();
53
$incoming_record->append_fields(
54
    MARC::Field->new('250', '', '', 'a' => '256 bottles of beer on the wall'), # Unchanged
55
    MARC::Field->new('250', '', '', 'a' => '251 bottles of beer on the wall'), # Appended
56
    # MARC::Field->new('250', '', '', 'a' => '250 bottles of beer on the wall'), # Removed
57
    # MARC::Field->new('500', '', '', 'a' => 'One bottle of beer in the fridge'), # Deleted
58
    MARC::Field->new('501', '', '', 'a' => 'One cold bottle of beer in the fridge'), # Added
59
    MARC::Field->new('501', '', '', 'a' => 'Two cold bottles of beer in the fridge'), # Added
60
);
61
62
# Test default behavior when MARCMergeRules is enabled, but no rules defined (overwrite)
63
subtest 'Record fields has been overwritten when no merge rules are defined' => sub {
64
    plan tests => 4;
65
66
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
67
68
    my @all_fields = $merged_record->fields();
69
70
    cmp_ok(scalar @all_fields, '==', 4, "Record has the expected number of fields");
71
    is_deeply(
72
        [map { $_->subfield('a') } $merged_record->field('250') ],
73
        ['256 bottles of beer on the wall', '251 bottles of beer on the wall'],
74
        '"250" fields has been appended and removed'
75
    );
76
77
    my @fields = $merged_record->field('500');
78
    cmp_ok(scalar @fields, '==', 0, '"500" field has been deleted');
79
80
    is_deeply(
81
        [map { $_->subfield('a') } $merged_record->field('501') ],
82
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
83
        '"501" fields has been added'
84
    );
85
};
86
87
my $rule =  Koha::MarcMergeRules->find_or_create({
88
    tag => '*',
89
    module => $modules[0]->name,
90
    filter => '*',
91
    add => 0,
92
    append => 0,
93
    remove => 0,
94
    delete => 0
95
});
96
97
subtest 'Record fields has been protected when matched merge all rule operations are set to "0"' => sub {
98
    plan tests => 3;
99
100
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
101
102
    my @all_fields = $merged_record->fields();
103
    cmp_ok(scalar @all_fields, '==', 3, "Record has the expected number of fields");
104
105
    is_deeply(
106
        [map { $_->subfield('a') } $merged_record->field('250') ],
107
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall'],
108
        '"250" fields has retained their original value'
109
    );
110
    is_deeply(
111
        [map { $_->subfield('a') } $merged_record->field('500') ],
112
        ['One bottle of beer in the fridge'],
113
        '"500" field has retained it\'s original value'
114
    );
115
};
116
117
subtest 'Only new fields has been added when add = 1, append = 0, remove = 0, delete = 0' => sub {
118
    plan tests => 4;
119
120
    $rule->set(
121
        {
122
            'add' => 1,
123
            'append' => 0,
124
            'remove' => 0,
125
            'delete' => 0,
126
        }
127
    );
128
    $rule->store();
129
130
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
131
132
    my @all_fields = $merged_record->fields();
133
    cmp_ok(scalar @all_fields, '==', 5, "Record has the expected number of fields");
134
135
    is_deeply(
136
        [map { $_->subfield('a') } $merged_record->field('250') ],
137
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall'],
138
        '"250" fields retain their original value'
139
    );
140
141
    is_deeply(
142
        [map { $_->subfield('a') } $merged_record->field('500') ],
143
        ['One bottle of beer in the fridge'],
144
        '"500" field retain it\'s original value'
145
    );
146
147
    is_deeply(
148
        [map { $_->subfield('a') } $merged_record->field('501') ],
149
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
150
        '"501" fields has been added'
151
    );
152
};
153
154
subtest 'Only appended fields has been added when add = 0, append = 1, remove = 0, delete = 0' => sub {
155
    plan tests => 3;
156
157
    $rule->set(
158
        {
159
            'add' => 0,
160
            'append' => 1,
161
            'remove' => 0,
162
            'delete' => 0,
163
        }
164
    );
165
    $rule->store();
166
167
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
168
169
    my @all_fields = $merged_record->fields();
170
    cmp_ok(scalar @all_fields, '==', 4, "Record has the expected number of fields");
171
172
    is_deeply(
173
        [map { $_->subfield('a') } $merged_record->field('250') ],
174
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall', '251 bottles of beer on the wall'],
175
        '"251" field has been appended'
176
    );
177
178
    is_deeply(
179
        [map { $_->subfield('a') } $merged_record->field('500') ],
180
        ['One bottle of beer in the fridge'],
181
        '"500" field has retained it\'s original value'
182
    );
183
184
};
185
186
subtest 'Appended and added fields has been added when add = 1, append = 1, remove = 0, delete = 0' => sub {
187
    plan tests => 4;
188
189
    $rule->set(
190
        {
191
            'add' => 1,
192
            'append' => 1,
193
            'remove' => 0,
194
            'delete' => 0,
195
        }
196
    );
197
    $rule->store();
198
199
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
200
201
    my @all_fields = $merged_record->fields();
202
    cmp_ok(scalar @all_fields, '==', 6, "Record has the expected number of fields");
203
204
    is_deeply(
205
        [map { $_->subfield('a') } $merged_record->field('250') ],
206
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall', '251 bottles of beer on the wall'],
207
        '"251" field has been appended'
208
    );
209
210
    is_deeply(
211
        [map { $_->subfield('a') } $merged_record->field('500') ],
212
        ['One bottle of beer in the fridge'],
213
        '"500" field has retained it\'s original value'
214
    );
215
216
    is_deeply(
217
        [map { $_->subfield('a') } $merged_record->field('501') ],
218
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
219
        '"501" fields has been added'
220
    );
221
};
222
223
subtest 'Record fields has been only removed when add = 0, append = 0, remove = 1, delete = 0' => sub {
224
    plan tests => 3;
225
226
    $rule->set(
227
        {
228
            'add' => 0,
229
            'append' => 0,
230
            'remove' => 1,
231
            'delete' => 0,
232
        }
233
    );
234
    $rule->store();
235
236
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
237
238
    my @all_fields = $merged_record->fields();
239
    cmp_ok(scalar @all_fields, '==', 2, "Record has the expected number of fields");
240
241
    is_deeply(
242
        [map { $_->subfield('a') } $merged_record->field('250') ],
243
        ['256 bottles of beer on the wall'],
244
        '"250" field has been removed'
245
    );
246
    is_deeply(
247
        [map { $_->subfield('a') } $merged_record->field('500') ],
248
        ['One bottle of beer in the fridge'],
249
        '"500" field has retained it\'s original value'
250
    );
251
};
252
253
subtest 'Record fields has been added and removed when add = 1, append = 0, remove = 1, delete = 0' => sub {
254
    plan tests => 4;
255
256
    $rule->set(
257
        {
258
            'add' => 1,
259
            'append' => 0,
260
            'remove' => 1,
261
            'delete' => 0,
262
        }
263
    );
264
    $rule->store();
265
266
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
267
268
    my @all_fields = $merged_record->fields();
269
    cmp_ok(scalar @all_fields, '==', 4, "Record has the expected number of fields");
270
271
    is_deeply(
272
        [map { $_->subfield('a') } $merged_record->field('250') ],
273
        ['256 bottles of beer on the wall'],
274
        '"250" field has been removed'
275
    );
276
    is_deeply(
277
        [map { $_->subfield('a') } $merged_record->field('500') ],
278
        ['One bottle of beer in the fridge'],
279
        '"500" field has retained it\'s original value'
280
    );
281
282
    is_deeply(
283
        [map { $_->subfield('a') } $merged_record->field('501') ],
284
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
285
        '"501" fields has been added'
286
    );
287
};
288
289
subtest 'Record fields has been appended and removed when add = 0, append = 1, remove = 1, delete = 0' => sub {
290
    plan tests => 3;
291
292
    $rule->set(
293
        {
294
            'add' => 0,
295
            'append' => 1,
296
            'remove' => 1,
297
            'delete' => 0,
298
        }
299
    );
300
    $rule->store();
301
302
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
303
304
    my @all_fields = $merged_record->fields();
305
    cmp_ok(scalar @all_fields, '==', 3, "Record has the expected number of fields");
306
307
    is_deeply(
308
        [map { $_->subfield('a') } $merged_record->field('250') ],
309
        ['256 bottles of beer on the wall', '251 bottles of beer on the wall'],
310
        '"250" fields has been appended and removed'
311
    );
312
    is_deeply(
313
        [map { $_->subfield('a') } $merged_record->field('500') ],
314
        ['One bottle of beer in the fridge'],
315
        '"500" field has retained it\'s original value'
316
    );
317
};
318
319
subtest 'Record fields has been added, appended and removed when add = 0, append = 1, remove = 1, delete = 0' => sub {
320
    plan tests => 4;
321
322
    $rule->set(
323
        {
324
            'add' => 1,
325
            'append' => 1,
326
            'remove' => 1,
327
            'delete' => 0,
328
        }
329
    );
330
    $rule->store();
331
332
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
333
334
    my @all_fields = $merged_record->fields();
335
    cmp_ok(scalar @all_fields, '==', 5, "Record has the expected number of fields");
336
337
    is_deeply(
338
        [map { $_->subfield('a') } $merged_record->field('250') ],
339
        ['256 bottles of beer on the wall', '251 bottles of beer on the wall'],
340
        '"250" fields has been appended and removed'
341
    );
342
343
    is_deeply(
344
        [map { $_->subfield('a') } $merged_record->field('500') ],
345
        ['One bottle of beer in the fridge'],
346
        '"500" field has retained it\'s original value'
347
    );
348
349
    is_deeply(
350
        [map { $_->subfield('a') } $merged_record->field('501') ],
351
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
352
        '"501" fields has been added'
353
    );
354
};
355
356
subtest 'Record fields has been deleted when add = 0, append = 0, remove = 0, delete = 1' => sub {
357
    plan tests => 2;
358
359
    $rule->set(
360
        {
361
            'add' => 0,
362
            'append' => 0,
363
            'remove' => 0,
364
            'delete' => 1,
365
        }
366
    );
367
    $rule->store();
368
369
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
370
371
    my @all_fields = $merged_record->fields();
372
    cmp_ok(scalar @all_fields, '==', 2, "Record has the expected number of fields");
373
374
    is_deeply(
375
        [map { $_->subfield('a') } $merged_record->field('250') ],
376
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall'],
377
        '"250" fields has retained their original value'
378
    );
379
};
380
381
subtest 'Record fields has been added and deleted when add = 1, append = 0, remove = 0, delete = 1' => sub {
382
    plan tests => 3;
383
384
    $rule->set(
385
        {
386
            'add' => 1,
387
            'append' => 0,
388
            'remove' => 0,
389
            'delete' => 1,
390
        }
391
    );
392
    $rule->store();
393
394
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
395
396
    my @all_fields = $merged_record->fields();
397
    cmp_ok(scalar @all_fields, '==', 4, "Record has the expected number of fields");
398
399
    is_deeply(
400
        [map { $_->subfield('a') } $merged_record->field('250') ],
401
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall'],
402
        '"250" fields has retained their original value'
403
    );
404
405
    is_deeply(
406
        [map { $_->subfield('a') } $merged_record->field('501') ],
407
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
408
        '"501" fields has been added'
409
    );
410
};
411
412
subtest 'Record fields has been appended and deleted when add = 0, append = 1, remove = 0, delete = 1' => sub {
413
    plan tests => 2;
414
415
    $rule->set(
416
        {
417
            'add' => 0,
418
            'append' => 1,
419
            'remove' => 0,
420
            'delete' => 1,
421
        }
422
    );
423
    $rule->store();
424
425
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
426
427
    my @all_fields = $merged_record->fields();
428
    cmp_ok(scalar @all_fields, '==', 3, "Record has the expected number of fields");
429
430
    is_deeply(
431
        [map { $_->subfield('a') } $merged_record->field('250') ],
432
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall', '251 bottles of beer on the wall'],
433
        '"250" field has been appended'
434
    );
435
};
436
437
subtest 'Record fields has been added, appended and deleted when add = 1, append = 1, remove = 0, delete = 1' => sub {
438
    plan tests => 3;
439
440
    $rule->set(
441
        {
442
            'add' => 1,
443
            'append' => 1,
444
            'remove' => 0,
445
            'delete' => 1,
446
        }
447
    );
448
    $rule->store();
449
450
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
451
452
    my @all_fields = $merged_record->fields();
453
    cmp_ok(scalar @all_fields, '==', 5, "Record has the expected number of fields");
454
455
    is_deeply(
456
        [map { $_->subfield('a') } $merged_record->field('250') ],
457
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall', '251 bottles of beer on the wall'],
458
        '"250" field has been appended'
459
    );
460
461
    is_deeply(
462
        [map { $_->subfield('a') } $merged_record->field('501') ],
463
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
464
        '"501" fields has been added'
465
    );
466
};
467
468
subtest 'Record fields has been removed and deleted when add = 0, append = 0, remove = 1, delete = 1' => sub {
469
    plan tests => 2;
470
471
    $rule->set(
472
        {
473
            'add' => 0,
474
            'append' => 0,
475
            'remove' => 1,
476
            'delete' => 1,
477
        }
478
    );
479
    $rule->store();
480
481
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
482
483
    my @all_fields = $merged_record->fields();
484
    cmp_ok(scalar @all_fields, '==', 1, "Record has the expected number of fields");
485
486
    is_deeply(
487
        [map { $_->subfield('a') } $merged_record->field('250') ],
488
        ['256 bottles of beer on the wall'],
489
        '"250" field has been removed'
490
    );
491
};
492
493
subtest 'Record fields has been added, removed and deleted when add = 1, append = 0, remove = 1, delete = 1' => sub {
494
    plan tests => 3;
495
496
    $rule->set(
497
        {
498
            'add' => 1,
499
            'append' => 0,
500
            'remove' => 1,
501
            'delete' => 1,
502
        }
503
    );
504
    $rule->store();
505
506
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
507
508
    my @all_fields = $merged_record->fields();
509
    cmp_ok(scalar @all_fields, '==', 3, "Record has the expected number of fields");
510
511
    is_deeply(
512
        [map { $_->subfield('a') } $merged_record->field('250') ],
513
        ['256 bottles of beer on the wall'],
514
        '"250" field has been removed'
515
    );
516
517
    is_deeply(
518
        [map { $_->subfield('a') } $merged_record->field('501') ],
519
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
520
        '"501" fields has been added'
521
    );
522
};
523
524
subtest 'Record fields has been appended, removed and deleted when add = 0, append = 1, remove = 1, delete = 1' => sub {
525
    plan tests => 2;
526
527
    $rule->set(
528
        {
529
            'add' => 0,
530
            'append' => 1,
531
            'remove' => 1,
532
            'delete' => 1,
533
        }
534
    );
535
    $rule->store();
536
537
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
538
539
    my @all_fields = $merged_record->fields();
540
    cmp_ok(scalar @all_fields, '==', 2, "Record has the expected number of fields");
541
542
    is_deeply(
543
        [map { $_->subfield('a') } $merged_record->field('250') ],
544
        ['256 bottles of beer on the wall', '251 bottles of beer on the wall'],
545
        '"250" fields has been appended and removed'
546
    );
547
};
548
549
subtest 'Record fields has been overwritten when add = 1, append = 1, remove = 1, delete = 1' => sub {
550
    plan tests => 4;
551
552
    $rule->set(
553
        {
554
            'add' => 1,
555
            'append' => 1,
556
            'remove' => 1,
557
            'delete' => 1,
558
        }
559
    );
560
    $rule->store();
561
562
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
563
564
    my @all_fields = $merged_record->fields();
565
566
    cmp_ok(scalar @all_fields, '==', 4, "Record has the expected number of fields");
567
    is_deeply(
568
        [map { $_->subfield('a') } $merged_record->field('250') ],
569
        ['256 bottles of beer on the wall', '251 bottles of beer on the wall'],
570
        '"250" fields has been appended and removed'
571
    );
572
573
    my @fields = $merged_record->field('500');
574
    cmp_ok(scalar @fields, '==', 0, '"500" field has been deleted');
575
576
    is_deeply(
577
        [map { $_->subfield('a') } $merged_record->field('501') ],
578
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
579
        '"501" fields has been added'
580
    );
581
};
582
583
# Test rule tag specificity
584
585
# Protect field 500 with more specific tag value
586
my $skip_all_rule = Koha::MarcMergeRules->find_or_create({
587
    tag => '500',
588
    module => $modules[0]->name,
589
    filter => '*',
590
    add => 0,
591
    append => 0,
592
    remove => 0,
593
    delete => 0
594
});
595
596
subtest '"500" field has been protected when rule matching on tag "500" is add = 0, append = 0, remove = 0, delete = 0' => sub {
597
    plan tests => 4;
598
599
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
600
601
    my @all_fields = $merged_record->fields();
602
603
    cmp_ok(scalar @all_fields, '==', 5, "Record has the expected number of fields");
604
    is_deeply(
605
        [map { $_->subfield('a') } $merged_record->field('250') ],
606
        ['256 bottles of beer on the wall', '251 bottles of beer on the wall'],
607
        '"250" fields has been appended and removed'
608
    );
609
610
    is_deeply(
611
        [map { $_->subfield('a') } $merged_record->field('500') ],
612
        ['One bottle of beer in the fridge'],
613
        '"500" field has retained it\'s original value'
614
    );
615
616
    is_deeply(
617
        [map { $_->subfield('a') } $merged_record->field('501') ],
618
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
619
        '"501" fields has been added'
620
    );
621
};
622
623
# Test regexp matching
624
subtest '"5XX" fields has been protected when rule matching on regexp "5\d{2}" is add = 0, append = 0, remove = 0, delete = 0' => sub {
625
    plan tests => 3;
626
627
    $skip_all_rule->set(
628
        {
629
            'tag' => '5\d{2}',
630
        }
631
    );
632
    $skip_all_rule->store();
633
634
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
635
636
    my @all_fields = $merged_record->fields();
637
638
    cmp_ok(scalar @all_fields, '==', 3, "Record has the expected number of fields");
639
    is_deeply(
640
        [map { $_->subfield('a') } $merged_record->field('250') ],
641
        ['256 bottles of beer on the wall', '251 bottles of beer on the wall'],
642
        '"250" fields has been appended and removed'
643
    );
644
645
    is_deeply(
646
        [map { $_->subfield('a') } $merged_record->field('500') ],
647
        ['One bottle of beer in the fridge'],
648
        '"500" field has retained it\'s original value'
649
    );
650
};
651
652
# Test module specificity, the 0 all rule should no longer be included in set of applied rules
653
subtest 'Record fields has been overwritten when non wild card rule with filter match is add = 1, append = 1, remove = 1, delete = 1' => sub {
654
    plan tests => 4;
655
656
    $rule->set(
657
        {
658
            'filter' => 'test',
659
        }
660
    );
661
    $rule->store();
662
663
    my $merged_record = Koha::MarcMergeRules->merge_records($orig_record, $incoming_record, { $modules[0]->name => 'test' });
664
665
    my @all_fields = $merged_record->fields();
666
667
    cmp_ok(scalar @all_fields, '==', 4, "Record has the expected number of fields");
668
    is_deeply(
669
        [map { $_->subfield('a') } $merged_record->field('250') ],
670
        ['256 bottles of beer on the wall', '251 bottles of beer on the wall'],
671
        '"250" fields has been appended and removed'
672
    );
673
674
    my @fields = $merged_record->field('500');
675
    cmp_ok(scalar @fields, '==', 0, '"500" field has been deleted');
676
677
    is_deeply(
678
        [map { $_->subfield('a') } $merged_record->field('501') ],
679
        ['One cold bottle of beer in the fridge', 'Two cold bottles of beer in the fridge'],
680
        '"501" fields has been added'
681
    );
682
};
683
684
subtest 'An exception is thrown when append = 1, remove = 0 is set for control field rule' => sub {
685
    plan tests => 2;
686
    my $exception = try {
687
        Koha::MarcMergeRules->validate({
688
            'tag' => '008',
689
            'append' => 1,
690
            'remove' => 0,
691
        });
692
    }
693
    catch {
694
        return $_;
695
    };
696
    ok(defined $exception, "Exception was caught");
697
    ok($exception->isa('Koha::Exceptions::MarcMergeRule::InvalidControlFieldActions'), "Exception is of correct class");
698
};
699
700
subtest 'An exception is thrown when rule tag is set to invalid regexp' => sub {
701
    plan tests => 2;
702
703
    my $exception = try {
704
        Koha::MarcMergeRules->validate({
705
            'tag' => '**'
706
        });
707
    }
708
    catch {
709
        return $_;
710
    };
711
    ok(defined $exception, "Exception was caught");
712
    ok($exception->isa('Koha::Exceptions::MarcMergeRule::InvalidTagRegExp'), "Exception is of correct class");
713
};
714
715
$skip_all_rule->delete();
716
717
subtest 'context option in ModBiblio is handled correctly' => sub {
718
    plan tests => 6;
719
    $rule->set(
720
        {
721
            tag => '250',
722
            module => $modules[0]->name,
723
            filter => '*',
724
            'add' => 0,
725
            'append' => 0,
726
            'remove' => 0,
727
            'delete' => 0,
728
        }
729
    );
730
    $rule->store();
731
    my ($biblionumber) = AddBiblio($orig_record, '');
732
733
    # Since marc merc rules are not run on save, only update
734
    # saved record should be identical to orig_record
735
    my $saved_record = GetMarcBiblio({ biblionumber => $biblionumber });
736
737
    my @all_fields = $saved_record->fields();
738
    # Koha also adds 999c field, therefore 4 not 3
739
    cmp_ok(scalar @all_fields, '==', 4, 'Saved record has the expected number of fields');
740
    is_deeply(
741
        [map { $_->subfield('a') } $saved_record->field('250') ],
742
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall'],
743
        'All "250" fields of saved record are identical to original record passed to AddBiblio'
744
    );
745
746
    is_deeply(
747
        [map { $_->subfield('a') } $saved_record->field('500') ],
748
        ['One bottle of beer in the fridge'],
749
        'All "500" fields of saved record are identical to original record passed to AddBiblio'
750
    );
751
752
    $saved_record->append_fields(
753
        MARC::Field->new('250', '', '', 'a' => '251 bottles of beer on the wall'), # Appended
754
        MARC::Field->new('500', '', '', 'a' => 'One cold bottle of beer in the fridge'), # Appended
755
    );
756
757
    ModBiblio($saved_record, $biblionumber, '', { context => { $modules[0]->name => 'test' } });
758
759
    my $updated_record = GetMarcBiblio({ biblionumber => $biblionumber });
760
761
    @all_fields = $updated_record->fields();
762
    cmp_ok(scalar @all_fields, '==', 5, 'Updated record has the expected number of fields');
763
    is_deeply(
764
        [map { $_->subfield('a') } $updated_record->field('250') ],
765
        ['250 bottles of beer on the wall', '256 bottles of beer on the wall'],
766
        '"250" fields have retained their original values'
767
    );
768
769
    is_deeply(
770
        [map { $_->subfield('a') } $updated_record->field('500') ],
771
        ['One bottle of beer in the fridge', 'One cold bottle of beer in the fridge'],
772
        '"500" field has been appended'
773
    );
774
775
    # To trigger removal from search index etc
776
    DelBiblio($biblionumber);
777
};
778
779
# Explicityly delete rule to trigger clearing of cache
780
$rule->delete();
781
782
$schema->storage->txn_rollback;
783
784
1;
(-)a/tools/batch_record_modification.pl (-4 / +10 lines)
Lines 169-175 if ( $op eq 'form' ) { Link Here
169
    my ( $job );
169
    my ( $job );
170
    if ( $runinbackground ) {
170
    if ( $runinbackground ) {
171
        my $job_size = scalar( @record_ids );
171
        my $job_size = scalar( @record_ids );
172
        $job = C4::BackgroundJob->new( $sessionID, "FIXME", '/cgi-bin/koha/tools/batch_record_modification.pl', $job_size );
172
        $job = C4::BackgroundJob->new( $sessionID, "FIXME", $ENV{SCRIPT_NAME}, $job_size );
173
        my $job_id = $job->id;
173
        my $job_id = $job->id;
174
        if (my $pid = fork) {
174
        if (my $pid = fork) {
175
            $dbh->{InactiveDestroy}  = 1;
175
            $dbh->{InactiveDestroy}  = 1;
Lines 181-187 if ( $op eq 'form' ) { Link Here
181
        } elsif (defined $pid) {
181
        } elsif (defined $pid) {
182
            close STDOUT;
182
            close STDOUT;
183
        } else {
183
        } else {
184
            warn "fork failed while attempting to run tools/batch_record_modification.pl as a background job";
184
            warn "fork failed while attempting to run $ENV{'SCRIPT_NAME'} as a background job";
185
            exit 0;
185
            exit 0;
186
        }
186
        }
187
    }
187
    }
Lines 205-211 if ( $op eq 'form' ) { Link Here
205
                my $record = GetMarcBiblio({ biblionumber => $biblionumber });
205
                my $record = GetMarcBiblio({ biblionumber => $biblionumber });
206
                ModifyRecordWithTemplate( $mmtid, $record );
206
                ModifyRecordWithTemplate( $mmtid, $record );
207
                my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
207
                my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
208
                ModBiblio( $record, $biblionumber, $frameworkcode );
208
                my ($member) = Koha::Patrons->find($loggedinuser);
209
                ModBiblio( $record, $biblionumber, $frameworkcode,
210
                    {
211
                        source => 'batchmod',
212
                        categorycode => $member->categorycode,
213
                        borrower => $loggedinuser
214
                    }
215
                );
209
            };
216
            };
210
            if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
217
            if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
211
                push @messages, {
218
                push @messages, {
212
- 

Return to bug 14957