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

(-)a/C4/Biblio.pm (-18 / +994 lines)
Lines 29-34 use MARC::File::USMARC; Link Here
29
use MARC::File::XML;
29
use MARC::File::XML;
30
use POSIX qw(strftime);
30
use POSIX qw(strftime);
31
use Module::Load::Conditional qw(can_load);
31
use Module::Load::Conditional qw(can_load);
32
use String::Similarity;
32
33
33
use C4::Koha;
34
use C4::Koha;
34
use C4::Log;    # logaction
35
use C4::Log;    # logaction
Lines 100-105 BEGIN { Link Here
100
      &CountBiblioInOrders
101
      &CountBiblioInOrders
101
      &GetSubscriptionsId
102
      &GetSubscriptionsId
102
      &GetHolds
103
      &GetHolds
104
105
      &GetMarcPermissionsRules
106
      &GetMarcPermissionsModules
107
      &ModMarcPermissionsRule
108
      &AddMarcPermissionsRule
109
      &DelMarcPermissionsRule
103
    );
110
    );
104
111
105
    # To modify something
112
    # To modify something
Lines 128-133 BEGIN { Link Here
128
    # they are useful in a few circumstances, so they are exported,
135
    # they are useful in a few circumstances, so they are exported,
129
    # but don't use them unless you are a core developer ;-)
136
    # but don't use them unless you are a core developer ;-)
130
    push @EXPORT, qw(
137
    push @EXPORT, qw(
138
      &ApplyMarcPermissions
131
      &ModBiblioMarc
139
      &ModBiblioMarc
132
    );
140
    );
133
141
Lines 225-239 bib to add, while the second argument is the desired MARC Link Here
225
framework code.
233
framework code.
226
234
227
This function also accepts a third, optional argument: a hashref
235
This function also accepts a third, optional argument: a hashref
228
to additional options.  The only defined option is C<defer_marc_save>,
236
to additional options. Defined options are:
229
which if present and mapped to a true value, causes C<AddBiblio>
237
230
to omit the call to save the MARC in C<bibilioitems.marc>
238
C<defer_marc_save>
231
and C<biblioitems.marcxml>  This option is provided B<only>
239
232
for the use of scripts such as C<bulkmarcimport.pl> that may need
240
    If present and mapped to a true value, causes C<AddBiblio> to omit the call
233
to do some manipulation of the MARC record for item parsing before
241
    to save the MARC in C<bibilioitems.marc> and C<biblioitems.marcxml>  This
234
saving it and which cannot afford the performance hit of saving
242
    option is provided B<only> for the use of scripts such as
235
the MARC record twice.  Consequently, do not use that option
243
    C<bulkmarcimport.pl> that may need to do some manipulation of the MARC
236
unless you can guarantee that C<ModBiblioMarc> will be called.
244
    record for item parsing before saving it and which cannot afford the
245
    performance hit of saving the MARC record twice.  Consequently, do not use
246
    that option unless you can guarantee that C<ModBiblioMarc> will be called.
247
248
C<filter>
249
250
    Optional hashref defining permission filters from the
251
    marc_permissions_modules table in form of {module => filter}. Three
252
    predefined filter modules exists:
253
254
        * source
255
        * category
256
        * borrower
237
257
238
=cut
258
=cut
239
259
Lines 266-272 sub AddBiblio { Link Here
266
    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
286
    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
267
287
268
    # now add the record
288
    # now add the record
269
    ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
289
    ModBiblioMarc( $record, $biblionumber, $frameworkcode, $options->{filter} ) unless $defer_marc_save;
270
290
271
    # update OAI-PMH sets
291
    # update OAI-PMH sets
272
    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
292
    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
Lines 295-306 in the C<biblio> and C<biblioitems> tables, as well as Link Here
295
which fields are used to store embedded item, biblioitem,
315
which fields are used to store embedded item, biblioitem,
296
and biblionumber data for indexing.
316
and biblionumber data for indexing.
297
317
318
This function also accepts a forth, optional argument: a hashref defining
319
permission filters from the marc_permissions_modules table in form of
320
{module => filter}. Three predefined filter modules exists:
321
322
    * source
323
    * category
324
    * borrower
325
298
Returns 1 on success 0 on failure
326
Returns 1 on success 0 on failure
299
327
300
=cut
328
=cut
301
329
302
sub ModBiblio {
330
sub ModBiblio {
303
    my ( $record, $biblionumber, $frameworkcode ) = @_;
331
    my ( $record, $biblionumber, $frameworkcode, $filter ) = @_;
332
304
    if (!$record) {
333
    if (!$record) {
305
        carp 'No record passed to ModBiblio';
334
        carp 'No record passed to ModBiblio';
306
        return 0;
335
        return 0;
Lines 338-350 sub ModBiblio { Link Here
338
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
367
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
339
368
340
    # load the koha-table data object
369
    # load the koha-table data object
341
    my $oldbiblio = TransformMarcToKoha( $dbh, $record, $frameworkcode );
370
    my $oldbiblio = TransformMarcToKoha( $dbh, $record, $frameworkcode, undef, $biblionumber, $filter );
342
371
343
    # update MARC subfield that stores biblioitems.cn_sort
372
    # update MARC subfield that stores biblioitems.cn_sort
344
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
373
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
345
374
346
    # update the MARC record (that now contains biblio and items) with the new record data
375
    # update the MARC record (that now contains biblio and items) with the new record data
347
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
376
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode, $filter);
348
377
349
    # modify the other koha tables
378
    # modify the other koha tables
350
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
379
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 1228-1234 $frameworkcode is optional. If not given, then the default framework is used. Link Here
1228
sub GetMarcSubfieldStructureFromKohaField {
1257
sub GetMarcSubfieldStructureFromKohaField {
1229
    my ($kohafield, $frameworkcode) = @_;
1258
    my ($kohafield, $frameworkcode) = @_;
1230
1259
1231
    return undef unless $kohafield;
1260
    return unless $kohafield;
1232
    $frameworkcode //= '';
1261
    $frameworkcode //= '';
1233
1262
1234
    my $dbh = C4::Context->dbh;
1263
    my $dbh = C4::Context->dbh;
Lines 2589-2595 our $inverted_field_map; Link Here
2589
2618
2590
=head2 TransformMarcToKoha
2619
=head2 TransformMarcToKoha
2591
2620
2592
  $result = TransformMarcToKoha( $dbh, $record, $frameworkcode )
2621
  $result = TransformMarcToKoha( $dbh, $record, $frameworkcode, $biblionumber, $filter )
2593
2622
2594
Extract data from a MARC bib record into a hashref representing
2623
Extract data from a MARC bib record into a hashref representing
2595
Koha biblio, biblioitems, and items fields. 
2624
Koha biblio, biblioitems, and items fields. 
Lines 2600-2606 hash_ref Link Here
2600
=cut
2629
=cut
2601
2630
2602
sub TransformMarcToKoha {
2631
sub TransformMarcToKoha {
2603
    my ( $dbh, $record, $frameworkcode, $limit_table ) = @_;
2632
    my ( $dbh, $record, $frameworkcode, $limit_table, $biblionumber, $filter ) = @_;
2604
2633
2605
    my $result = {};
2634
    my $result = {};
2606
    if (!defined $record) {
2635
    if (!defined $record) {
Lines 2623-2628 sub TransformMarcToKoha { Link Here
2623
        $tables{'biblioitems'} = 1;
2652
        $tables{'biblioitems'} = 1;
2624
    }
2653
    }
2625
2654
2655
    # apply permissions
2656
    if ( C4::Context->preference('MARCPermissions') && $biblionumber && $filter) {
2657
        $record = ApplyMarcPermissions({
2658
                biblionumber => $biblionumber,
2659
                record => $record,
2660
                frameworkcode => $frameworkcode,
2661
                filter => $filter,
2662
                nolog => 1
2663
            });
2664
    }
2665
2626
    # traverse through record
2666
    # traverse through record
2627
  MARCFIELD: foreach my $field ( $record->fields() ) {
2667
  MARCFIELD: foreach my $field ( $record->fields() ) {
2628
        my $tag = $field->tag();
2668
        my $tag = $field->tag();
Lines 3389-3395 sub _koha_delete_biblioitems { Link Here
3389
3429
3390
=head2 ModBiblioMarc
3430
=head2 ModBiblioMarc
3391
3431
3392
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3432
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode,$filter);
3393
3433
3394
Add MARC data for a biblio to koha 
3434
Add MARC data for a biblio to koha 
3395
3435
Lines 3400-3406 Function exported, but should NOT be used, unless you really know what you're do Link Here
3400
sub ModBiblioMarc {
3440
sub ModBiblioMarc {
3401
    # pass the MARC::Record to this function, and it will create the records in
3441
    # pass the MARC::Record to this function, and it will create the records in
3402
    # the marc field
3442
    # the marc field
3403
    my ( $record, $biblionumber, $frameworkcode ) = @_;
3443
    my ( $record, $biblionumber, $frameworkcode, $filter ) = @_;
3444
3404
    if ( !$record ) {
3445
    if ( !$record ) {
3405
        carp 'ModBiblioMarc passed an undefined record';
3446
        carp 'ModBiblioMarc passed an undefined record';
3406
        return;
3447
        return;
Lines 3446-3451 sub ModBiblioMarc { Link Here
3446
      $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3487
      $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3447
    }
3488
    }
3448
3489
3490
    # apply permissions
3491
    if ( C4::Context->preference('MARCPermissions') ) {
3492
        $record = ApplyMarcPermissions({
3493
                biblionumber => $biblionumber,
3494
                record => $record,
3495
                frameworkcode => $frameworkcode,
3496
                filter => $filter
3497
            });
3498
    }
3499
3449
    $sth = $dbh->prepare("UPDATE biblioitems SET marc=?,marcxml=? WHERE biblionumber=?");
3500
    $sth = $dbh->prepare("UPDATE biblioitems SET marc=?,marcxml=? WHERE biblionumber=?");
3450
    $sth->execute( $record->as_usmarc(), $record->as_xml_record($encoding), $biblionumber );
3501
    $sth->execute( $record->as_usmarc(), $record->as_xml_record($encoding), $biblionumber );
3451
    $sth->finish;
3502
    $sth->finish;
Lines 3804-3809 sub RemoveAllNsb { Link Here
3804
    return $record;
3855
    return $record;
3805
}
3856
}
3806
3857
3858
=head2 ApplyMarcPermissions
3859
3860
    my $record = ApplyMarcPermissions($arguments)
3861
3862
Applies marc permission rules to a record.
3863
3864
C<$arguments> is expected to be a hashref with below keys defined.
3865
3866
=over 4
3867
3868
=item C<biblionumber>
3869
biblionumber of old record
3870
3871
=item C<record>
3872
record that will modify old record
3873
3874
=item C<frameworkcode>
3875
only tags included in framework will be processed
3876
3877
=item C<filter>
3878
hashref containing at least one filter module from the marc_permissions_modules
3879
table in form {module => filter}. Three predefined filter modules exists:
3880
3881
    * source
3882
    * category
3883
    * borrower
3884
3885
=item C<log>
3886
optional reference to array that will be filled with rule evaluation log
3887
entries.
3888
3889
=item C<nolog>
3890
optional boolean which when true disables logging to action log.
3891
3892
=back
3893
3894
Returns:
3895
3896
=over 4
3897
3898
=item C<$record>
3899
3900
new MARC record based on C<record> with C<filter> applied. If no old
3901
record for C<biblionumber> can be found, C<record> is returned unchanged.
3902
Default action when no matching filter found is to leave old record unchanged.
3903
3904
=back
3905
3906
=cut
3907
3908
sub ApplyMarcPermissions {
3909
    my $arguments     = shift;
3910
    my $biblionumber  = $arguments->{biblionumber};
3911
    my $new_record    = $arguments->{record};
3912
    my $frameworkcode = $arguments->{frameworkcode} || '';
3913
3914
    if ( !$biblionumber ) {
3915
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3916
        return;
3917
    }
3918
    if ( !$new_record ) {
3919
        carp 'ApplyMarcPermissions called on undefined record';
3920
        return;
3921
    }
3922
    my $filter = $arguments->{filter}
3923
      or return $new_record;
3924
3925
    my $log   = $arguments->{log} || [];
3926
    my $nolog = $arguments->{nolog};
3927
3928
    my $dbh = C4::Context->dbh;
3929
3930
    my $is_regex = sub {
3931
        my ( $tag, $m ) = @_;
3932
        # tag is not exactly same as possible regex
3933
        $tag ne $m &&
3934
3935
        # wildcard
3936
        $m ne '*' &&
3937
3938
        # valid tagDataType
3939
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3940
        ([1-9A-z][\dA-z]{2})$/x &&
3941
3942
        # nor valid controltagDataType
3943
        $m !~ /00[1-9A-Za-z]{1}/ &&
3944
3945
        # so we try it as a regex
3946
        $tag =~ /^$m$/
3947
    };
3948
3949
    my $old_record = GetMarcBiblio($biblionumber);
3950
    if ( $old_record ) {
3951
        my $ret_record      = MARC::Record->new();
3952
        my $perm            = GetMarcPermissions() or return $new_record;
3953
        my $marc_struct     = GetMarcStructure(1, $frameworkcode);
3954
        my $similarity_pref = C4::Context->preference("MARCPermissionsCorrectionSimilarity");
3955
        $similarity_pref    = defined $similarity_pref &&
3956
                              $similarity_pref >= 0 &&
3957
                              $similarity_pref <= 100 ?
3958
                              $similarity_pref/100.0 : undef;
3959
3960
        # First came the leader ...
3961
        $ret_record->leader($old_record->leader());
3962
3963
        # ... and then came all the tags in the framework
3964
        for my $tag (sort keys %{$marc_struct}) {
3965
            my @old_fields = $old_record->field($tag);
3966
            my @new_fields = $new_record->field($tag);
3967
            next unless @old_fields or @new_fields;
3968
3969
            my @perms = (
3970
                $perm->{'*'},
3971
                (   # regex tags
3972
                    map { $perm->{$_} }
3973
                      grep { $is_regex->( $tag, $_ ) } sort keys %{$perm}
3974
                ),
3975
                $perm->{$tag}
3976
            );
3977
3978
            # There can be more than one field for each tag, so we have to
3979
            # iterate all fields and distinguish which are new, removed or
3980
            # modified
3981
            my $max_fields = ($#old_fields, $#new_fields)
3982
                             [$#old_fields< $#new_fields];
3983
            for ( my $i_field = 0; $i_field <= $max_fields; $i_field++ ) {
3984
                my $old_field = $old_fields[$i_field];
3985
                my $new_field = $new_fields[$i_field];
3986
3987
                # Existing field
3988
                if ( $old_field and $new_field ) {
3989
                    # Control fields
3990
                    if ( $old_field->is_control_field() ) {
3991
                        # Existing control field
3992
                        if ( $old_field and $new_field ) {
3993
                            my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @perms);
3994
                            if ( defined $on_existing->{action} ) {
3995
                                push @{$log},
3996
                                  {
3997
                                    rule         => $on_existing->{rule},
3998
                                    event        => "existing",
3999
                                    action       => $on_existing->{action},
4000
                                    tag          => $tag,
4001
                                    subfieldcode => undef,
4002
                                    filter       => $filter
4003
                                  };
4004
                                if ( $on_existing->{action} eq 'skip' ) {
4005
                                    $ret_record->insert_fields_ordered($old_field);
4006
                                    next;
4007
                                } elsif ( $on_existing->{action} eq 'overwrite' ) {
4008
                                    $ret_record->insert_fields_ordered($new_field);
4009
                                    next;
4010
                                } elsif ( $on_existing->{action} eq 'add' ) {
4011
                                    $ret_record->insert_fields_ordered($old_field);
4012
                                    $ret_record->insert_fields_ordered($new_field);
4013
                                    next;
4014
                                } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
4015
                                    if ( $similarity_pref ) {
4016
                                        my $similarity = similarity $old_field->data(), $new_field->data();
4017
                                        if ( $similarity >= $similarity_pref ) {
4018
                                            $ret_record->insert_fields_ordered($new_field);
4019
                                        } else {
4020
                                            $ret_record->insert_fields_ordered($old_field);
4021
                                            $ret_record->insert_fields_ordered($new_field);
4022
                                        }
4023
                                    } else {
4024
                                        $ret_record->insert_fields_ordered($old_field);
4025
                                    }
4026
                                }
4027
                            } else { # default to skip
4028
                                $ret_record->insert_fields_ordered($old_field);
4029
                                next;
4030
                            }
4031
                        # New control field
4032
                        } elsif ( not $old_field and $new_field ) {
4033
                            my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
4034
                            if ( defined $on_new->{action} ) {
4035
                                push @{$log},
4036
                                  {
4037
                                    rule         => $on_new->{rule},
4038
                                    event        => "new",
4039
                                    action       => $on_new->{action},
4040
                                    tag          => $tag,
4041
                                    subfieldcode => undef,
4042
                                    filter       => $filter
4043
                                  };
4044
                                if ( $on_new->{action} eq 'skip' ) {
4045
                                    next;
4046
                                } elsif ( $on_new->{action} eq 'add' ) {
4047
                                    $ret_record->insert_fields_ordered($new_field);
4048
                                    next;
4049
                                }
4050
                            }
4051
                        # Removed control field
4052
                        } elsif ( $old_field and not $new_field ) {
4053
                            my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4054
                            if ( defined $on_removed->{action} ) {
4055
                                push @{$log},
4056
                                  {
4057
                                    rule         => $on_removed->{rule},
4058
                                    event        => "removed",
4059
                                    action       => $on_removed->{action},
4060
                                    tag          => $tag,
4061
                                    subfieldcode => undef
4062
                                  };
4063
                                if ( $on_removed->{action} eq 'skip' ) {
4064
                                    $ret_record->insert_fields_ordered($old_field);
4065
                                    next;
4066
                                } elsif ( $on_removed->{action} eq 'remove' ) {
4067
                                    next;
4068
                                }
4069
                            } else { # default to skip
4070
                                $ret_record->insert_fields_ordered($old_field);
4071
                                next;
4072
                            }
4073
                        }
4074
                    # Indicators and subfields
4075
                    } else {
4076
                        # Indicators
4077
                        my @old_ind = map { $old_field->indicator($_) } (1,2);
4078
                        my @new_ind = map { $new_field->indicator($_) } (1,2);
4079
                        my @ind = ('','');
4080
4081
                        for my $i ((0,1)) {
4082
                            my @ind_perms = (
4083
                                @perms,
4084
                                $perm->{$tag}->{subfields}->{ 'i' . ( $i + 1 ) }
4085
                            );
4086
4087
                            # Existing indicator
4088
                            if ( defined $old_ind[$i] and defined $new_ind[$i] ) {
4089
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @ind_perms);
4090
                                if ( defined $on_existing->{action} ) {
4091
                                    push @{$log},
4092
                                      {
4093
                                        rule         => $on_existing->{rule},
4094
                                        event        => "existing",
4095
                                        action       => $on_existing->{action},
4096
                                        tag          => $tag,
4097
                                        subfieldcode => 'i' . ( $i + 1 ),
4098
                                        filter       => $filter
4099
                                      };
4100
                                    if ( $on_existing->{action} eq 'skip' ) {
4101
                                        $ind[$i] = $old_ind[$i];
4102
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4103
                                        $ind[$i] = $new_ind[$i];
4104
                                    }
4105
                                } else { # default to skip
4106
                                    $ind[$i] = $old_ind[$i];
4107
                                }
4108
                            # New indicator
4109
                            } elsif ( not defined $old_ind[$i] and defined $new_ind[$i] ) {
4110
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @ind_perms);
4111
                                if ( defined $on_new->{action} ) {
4112
                                    push @{$log},
4113
                                      {
4114
                                        rule         => $on_new->{rule},
4115
                                        event        => "new",
4116
                                        action       => $on_new->{action},
4117
                                        tag          => $tag,
4118
                                        subfieldcode => 'i' . ( $i + 1 ),
4119
                                        filter       => $filter
4120
                                      };
4121
                                    if ( $on_new->{action} eq 'skip' ) {
4122
                                        $ind[$i] = $old_ind[$i];
4123
                                    } elsif ( $on_new->{action} eq 'add' ) {
4124
                                        $ind[$i] = $new_ind[$i];
4125
                                    }
4126
                                }
4127
                            # Removed indicator
4128
                            } elsif ( defined $old_ind[$i] and not defined $new_ind[$i] ) {
4129
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @ind_perms);
4130
                                if ( defined $on_removed->{action} ) {
4131
                                    push @{$log},
4132
                                      {
4133
                                        rule         => $on_removed->{rule},
4134
                                        event        => "removed",
4135
                                        action       => $on_removed->{action},
4136
                                        tag          => $tag,
4137
                                        subfieldcode => 'i' . ( $i + 1 ),
4138
                                        filter       => $filter
4139
                                      };
4140
                                    if ( $on_removed->{action} eq 'skip' ) {
4141
                                        $ind[$i] = $old_ind[$i];
4142
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4143
                                        $ind[$i] = '';
4144
                                    }
4145
                                } else { # default to skip
4146
                                    $ind[$i] = $old_ind[$i];
4147
                                }
4148
                            }
4149
                        }
4150
4151
                        # Subfields
4152
                        my @subfields = ();
4153
                        my %proccessed_subcodes = ();
4154
4155
                        # Try to preserve subfield order by first processing
4156
                        # subfields from the old record, and then subfields from
4157
                        # the framework structure unless include in old record.
4158
                        my @sorted_subfields = (
4159
                            map { shift @{$_} } $old_field->subfields(),
4160
                            () = sort keys %{ $marc_struct->{$tag} }
4161
                        );
4162
                        for my $subcode ( @sorted_subfields ) {
4163
                            next if $proccessed_subcodes{$subcode};
4164
                            $proccessed_subcodes{$subcode} = 1;
4165
                            next unless ref $marc_struct->{$tag}{$subcode} eq 'HASH';
4166
                            my @old_subfields = $old_field->subfield($subcode);
4167
                            my @new_subfields = $new_field->subfield($subcode);
4168
                            next unless @old_subfields or @new_subfields;
4169
4170
                            my @subfield_perms = (
4171
                                @perms,
4172
                                $perm->{'*'}->{subfields}->{'*'},
4173
                                $perm->{$tag}->{subfields}->{'*'},
4174
                                (    # tag->regex
4175
                                    map { $perm->{$tag}->{subfields}->{$_} }
4176
                                      grep {
4177
                                        # subcode is not exactly same as
4178
                                        # possible regex
4179
                                        $subcode ne $_ &&
4180
4181
                                        # wildcard, not checking for valid
4182
                                        # subfieldcodeDataType since it
4183
                                        # contains too many regex characters
4184
                                        $_ !~ /^(\*)$/ &&
4185
4186
                                        # so we try it as a regex
4187
                                        $subcode =~ /$_/
4188
                                      } sort
4189
                                      keys %{ $perm->{$tag}->{subfields} }
4190
                                ),
4191
                                (    # regex->*
4192
                                    map { $perm->{$_}->{subfields}->{'*'} }
4193
                                      grep { $is_regex->( $tag, $_ ) }
4194
                                      sort keys %{$perm}
4195
                                ),
4196
                                $perm->{'*'}->{subfields}->{$subcode},
4197
                                (    # regex->subcode
4198
                                    map { $perm->{$_}->{subfields}->{$subcode} }
4199
                                      grep { $is_regex->( $tag, $_ ) }
4200
                                      sort keys %{$perm}
4201
                                ),
4202
                                $perm->{$tag}->{subfields}->{$subcode}
4203
                            );
4204
4205
                            # Existing subfield
4206
                            if ( @old_subfields and @new_subfields ) {
4207
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @subfield_perms);
4208
                                if ( defined $on_existing->{action} ) {
4209
                                    push @{$log},
4210
                                      {
4211
                                        rule         => $on_existing->{rule},
4212
                                        event        => "existing",
4213
                                        action       => $on_existing->{action},
4214
                                        tag          => $tag,
4215
                                        subfieldcode => $subcode,
4216
                                        filter       => $filter
4217
                                      };
4218
                                    if ( $on_existing->{action} eq 'skip' ) {
4219
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4220
                                        next;
4221
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4222
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4223
                                        next;
4224
                                    } elsif ( $on_existing->{action} eq 'add' ) {
4225
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4226
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4227
                                        next;
4228
                                    } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
4229
                                        if ( $similarity_pref ) {
4230
                                            my @corrected_vals = ();
4231
4232
                                            # correct all old subfields
4233
                                            for ( my $i_new = 0; $i_new <= $#new_subfields; $i_new++ ) {
4234
                                                my $new_val = $new_subfields[$i_new];
4235
                                                for ( my $i_old = 0; $i_old <= $#old_subfields; $i_old++ ) {
4236
                                                    my $old_val = $old_subfields[$i_old];
4237
                                                    next if not defined $old_val or not defined $new_val;
4238
                                                    my $similarity = similarity $old_val, $new_val;
4239
                                                    if ( $similarity >= $similarity_pref ) {
4240
                                                        $corrected_vals[$i_old] = $new_val;
4241
                                                        $new_subfields[$i_new] = undef;
4242
                                                        $old_subfields[$i_old] = undef;
4243
                                                        last;
4244
                                                    }
4245
                                                }
4246
                                            }
4247
4248
                                            # insert all unchanged old subfields
4249
                                            map {
4250
                                                $corrected_vals[$_] = $old_subfields[$_]
4251
                                                    if defined $old_subfields[$_]
4252
                                                } 0 .. $#old_subfields;
4253
4254
4255
                                            # append all remaning new subfields
4256
                                            map {push @corrected_vals, $_ if defined} @new_subfields;
4257
4258
                                            push(@subfields, map {($subcode,$_)} @corrected_vals);
4259
                                        } else {
4260
                                            push(@subfields, map {($subcode,$_)} @old_subfields);
4261
                                        }
4262
                                    }
4263
                                } else { # default to skip
4264
                                    push(@subfields, map {($subcode,$_)} @old_subfields);
4265
                                    next;
4266
                                }
4267
                            # New subfield
4268
                            } elsif ( not @old_subfields and @new_subfields ) {
4269
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @subfield_perms);
4270
                                if ( defined $on_new->{action} ) {
4271
                                    push @{$log},
4272
                                      {
4273
                                        rule         => $on_new->{rule},
4274
                                        event        => "new",
4275
                                        action       => $on_new->{action},
4276
                                        tag          => $tag,
4277
                                        subfieldcode => $subcode,
4278
                                        filter       => $filter
4279
                                      };
4280
                                    if ( $on_new->{action} eq 'skip' ) {
4281
                                        next;
4282
                                    } elsif ( $on_new->{action} eq 'add' ) {
4283
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4284
                                        next;
4285
                                    }
4286
                                }
4287
                            # Removed subfield
4288
                            } elsif ( @old_subfields and not @new_subfields ) {
4289
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @subfield_perms);
4290
                                if ( defined $on_removed->{action} ) {
4291
                                    push @{$log},
4292
                                      {
4293
                                        rule         => $on_removed->{rule},
4294
                                        event        => "removed",
4295
                                        action       => $on_removed->{action},
4296
                                        tag          => $tag,
4297
                                        subfieldcode => $subcode,
4298
                                        filter       => $filter
4299
                                      };
4300
                                    if ( $on_removed->{action} eq 'skip' ) {
4301
                                        push(@subfields, ($subcode => @old_subfields));
4302
                                        next;
4303
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4304
                                        next;
4305
                                    }
4306
                                } else { # default to skip
4307
                                    push(@subfields, ($subcode => @old_subfields));
4308
                                    next;
4309
                                }
4310
                            }
4311
                        } # / for each subfield
4312
                        $ret_record->insert_grouped_field(MARC::Field->new($tag, $ind[0], $ind[1], @subfields));
4313
                    }
4314
                # New field
4315
                } elsif ( not $old_field and $new_field ) {
4316
                    my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
4317
                    if ( defined $on_new->{action} ) {
4318
                        push @{$log},
4319
                          {
4320
                            rule         => $on_new->{rule},
4321
                            event        => "new",
4322
                            action       => $on_new->{action},
4323
                            tag          => $tag,
4324
                            subfieldcode => undef,
4325
                            filter       => $filter
4326
                          };
4327
                        if ( $on_new->{action} eq 'skip' ) {
4328
                            next;
4329
                        } elsif ( $on_new->{action} eq 'add' ) {
4330
                            $ret_record->insert_fields_ordered($new_field);
4331
                            next;
4332
                        }
4333
                    }
4334
                # Removed field
4335
                } elsif ( $old_field and not $new_field ) {
4336
                    my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4337
                    if ( defined $on_removed->{action} ) {
4338
                        push @{$log},
4339
                          {
4340
                            rule         => $on_removed->{rule},
4341
                            event        => "removed",
4342
                            action       => $on_removed->{action},
4343
                            tag          => $tag,
4344
                            subfieldcode => undef,
4345
                            filter       => $filter
4346
                          };
4347
                        if ( $on_removed->{action} eq 'skip' ) {
4348
                            $ret_record->insert_grouped_field($old_field);
4349
                            next;
4350
                        } elsif ( $on_removed->{action} eq 'remove' ) {
4351
                            next;
4352
                        }
4353
                    } else { # default to skip
4354
                        $ret_record->insert_grouped_field($old_field);
4355
                        next;
4356
                    }
4357
                }
4358
            } # / for each field
4359
        } # / for each tag
4360
        if ( !$nolog && C4::Context->preference("MARCPermissionsLog") ) {
4361
            my $log_str = '';
4362
            my $n       = $#{$log};
4363
            for my $l ( @{$log} ) {
4364
                $log_str .= "{\n";
4365
                my $kn = keys %{$l};
4366
                for my $k ( sort keys %{$l} ) {
4367
                    if ( $k eq 'filter' ) {
4368
                        $log_str .= "  filter => {\n";
4369
                        my $fkn = keys %{ $l->{$k} };
4370
                        for my $fk ( sort keys %{ $l->{$k} } ) {
4371
                            $log_str .= '    '
4372
                              . $fk . ' => '
4373
                              . $l->{$k}->{$fk}
4374
                              . ( --$fkn ? ',' : '' ) . "\n";
4375
                        }
4376
                        $log_str .= "  }" . ( --$kn ? ',' : '' ) . "\n";
4377
                    }
4378
                    else {
4379
                        $log_str .= '  '
4380
                          . $k . ' => '
4381
                          . $l->{$k}
4382
                          . ( --$kn ? ',' : '' ) . "\n"
4383
                          if defined $l->{$k};
4384
                    }
4385
                }
4386
                $log_str .= '}' . ( $n-- ? ",\n" : '' );
4387
            }
4388
            logaction( "CATALOGUING", "MODIFY", $biblionumber, $log_str );
4389
        }
4390
        return $ret_record;
4391
    }
4392
    return $new_record;
4393
}
4394
4395
4396
=head2 GetMarcPermissions
4397
4398
    my $marc_permissions = GetMarcPermissions()
4399
4400
Loads MARC field permissions from the marc_permissions table.
4401
4402
Returns:
4403
4404
=over 4
4405
4406
=item C<$marc_permissions>
4407
4408
hashref with permissions structure for use with GetMarcPermissionsAction.
4409
4410
=back
4411
4412
=cut
4413
4414
sub GetMarcPermissions {
4415
    my $dbh = C4::Context->dbh;
4416
    my $rule_count = 0;
4417
    my %perms = ();
4418
4419
    my $query = '
4420
    SELECT `marc_permissions`.*,
4421
           `marc_permissions_modules`.`name`,
4422
           `marc_permissions_modules`.`description`,
4423
           `marc_permissions_modules`.`specificity`
4424
    FROM `marc_permissions`
4425
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4426
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4427
    ';
4428
    my $sth = $dbh->prepare($query);
4429
    $sth->execute();
4430
    while ( my $row = $sth->fetchrow_hashref ) {
4431
        $rule_count++;
4432
        if ( defined $row->{tagsubfield} and $row->{tagsubfield} ) {
4433
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4434
              ->{on_existing}->{ $row->{name} }->{ $row->{filter} } =
4435
              { action => $row->{on_existing}, rule => $row->{'id'} };
4436
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4437
              ->{on_new}->{ $row->{name} }->{ $row->{filter} } =
4438
              { action => $row->{on_new}, rule => $row->{'id'} };
4439
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4440
              ->{on_removed}->{ $row->{name} }->{ $row->{filter} } =
4441
              { action => $row->{on_removed}, rule => $row->{'id'} };
4442
        }
4443
        else {
4444
            $perms{ $row->{tagfield} }->{on_existing}->{ $row->{name} }
4445
              ->{ $row->{filter} } =
4446
              { action => $row->{on_existing}, rule => $row->{'id'} };
4447
            $perms{ $row->{tagfield} }->{on_new}->{ $row->{name} }
4448
              ->{ $row->{filter} } =
4449
              { action => $row->{on_new}, rule => $row->{'id'} };
4450
            $perms{ $row->{tagfield} }->{on_removed}->{ $row->{name} }
4451
              ->{ $row->{filter} } =
4452
              { action => $row->{on_removed}, rule => $row->{'id'} };
4453
        }
4454
    }
4455
4456
    return unless $rule_count;
4457
    return \%perms;
4458
}
4459
4460
=head2 GetMarcPermissionsAction
4461
4462
    my $action = GetMarcPermissionsAction($event, $filter, @permissions)
4463
4464
Gets action based on C<$event>, C<$filter> and C<@permissions>.
4465
4466
=over 4
4467
4468
=item C<$event>
4469
4470
which event: 'on_existing', 'on_new' or 'on_removed'
4471
4472
=item C<$filter>
4473
4474
hashref containing at least one filter module from the marc_permissions_modules
4475
table in form {module => filter}. Three predefined filter modules exists:
4476
4477
    * source
4478
    * category
4479
    * borrower
4480
4481
=item C<@permissions>
4482
4483
list of permission structures in order of specificity from least to most
4484
specific.
4485
4486
=back
4487
4488
Returns:
4489
4490
=over 4
4491
4492
=item C<$action>
4493
4494
hashref defining matching action.
4495
4496
=back
4497
4498
=cut
4499
4500
sub GetMarcPermissionsAction {
4501
    my $what = shift or return;
4502
    my $filter = shift or return;
4503
    my @perms = @_;
4504
4505
    my $modules = GetMarcPermissionsModules();
4506
4507
    my $action = undef;
4508
    for my $perm (@perms) {
4509
        next if not defined $perm;
4510
        next if not defined $perm->{$what};
4511
        my $tmp_action = undef;
4512
4513
        for my $module ( @{$modules} ) {
4514
            if (defined $perm->{$what}->{ $module->{'name'} }
4515
                and
4516
                defined $perm->{$what}->{ $module->{'name'} }->{'*'}->{action} )
4517
            {
4518
                $tmp_action = $perm->{$what}->{ $module->{'name'} }->{'*'};
4519
            }
4520
            if (    defined $filter->{ $module->{'name'} }
4521
                and defined $perm->{$what}->{ $module->{'name'} }
4522
                and defined $perm->{$what}->{ $module->{'name'} }
4523
                ->{ $filter->{ $module->{'name'} } }->{action} )
4524
            {
4525
                $tmp_action = $perm->{$what}->{ $module->{'name'} }
4526
                  ->{ $filter->{ $module->{'name'} } };
4527
            }
4528
        }
4529
4530
        $action = $tmp_action if defined $tmp_action;
4531
    }
4532
4533
    return $action;
4534
}
4535
4536
=head2 GetMarcPermissionsRules
4537
4538
    my $rules = GetMarcPermissionsRules()
4539
4540
Returns:
4541
4542
=over 4
4543
4544
=item C<$rules>
4545
4546
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
4547
table in order of module specificity and rule id.
4548
4549
=back
4550
4551
=cut
4552
4553
sub GetMarcPermissionsRules {
4554
    my $dbh = C4::Context->dbh;
4555
    my @rules = ();
4556
4557
    my $query = '
4558
    SELECT `marc_permissions`.`id`,
4559
           `marc_permissions`.`tagfield`,
4560
           `marc_permissions`.`tagsubfield`,
4561
           `marc_permissions`.`filter`,
4562
           `marc_permissions`.`on_existing`,
4563
           `marc_permissions`.`on_new`,
4564
           `marc_permissions`.`on_removed`,
4565
           `marc_permissions_modules`.`name` as  `module`,
4566
           `marc_permissions_modules`.`description`,
4567
           `marc_permissions_modules`.`specificity`
4568
    FROM `marc_permissions`
4569
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4570
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4571
    ';
4572
    my $sth = $dbh->prepare($query);
4573
    $sth->execute();
4574
    while ( my $row = $sth->fetchrow_hashref ) {
4575
        push(@rules, $row);
4576
    }
4577
4578
    return wantarray ? @rules : \@rules;
4579
}
4580
4581
=head2 GetMarcPermissionsModules
4582
4583
    my $modules = GetMarcPermissionsModules()
4584
4585
Returns:
4586
4587
=over 4
4588
4589
=item C<$modules>
4590
4591
array (in list context, arrayref otherwise) of hashrefs from
4592
marc_permissions_modules table in order of specificity.
4593
4594
=back
4595
4596
=cut
4597
4598
sub GetMarcPermissionsModules {
4599
    my $dbh = C4::Context->dbh;
4600
    my @modules = ();
4601
4602
    my $query = '
4603
    SELECT *
4604
    FROM `marc_permissions_modules`
4605
    ORDER BY `specificity`
4606
    ';
4607
    my $sth = $dbh->prepare($query);
4608
    $sth->execute();
4609
    while ( my $row = $sth->fetchrow_hashref ) {
4610
        push(@modules, $row);
4611
    }
4612
4613
    return wantarray ? @modules : \@modules;
4614
}
4615
4616
=head2 ModMarcPermissionsRule
4617
4618
    my $success = ModMarcPermissionsRule($id, $fields)
4619
4620
Modifies rule in the marc_permissions table.
4621
4622
=over 4
4623
4624
=item C<$id>
4625
4626
rule id to modify
4627
4628
=item C<$fields>
4629
4630
hashref defining the table fields
4631
4632
      * tagfield - required
4633
      * tagsubfield
4634
      * module - required
4635
      * filter - required
4636
      * on_existing - required
4637
      * on_new - required
4638
      * on_removed - required
4639
4640
=back
4641
4642
Returns:
4643
4644
=over 4
4645
4646
=item C<$success>
4647
4648
undef if an error occurs, otherwise true.
4649
4650
=back
4651
4652
=cut
4653
4654
sub ModMarcPermissionsRule {
4655
    my ($id, $f) = @_;
4656
    my $dbh = C4::Context->dbh;
4657
    my $query = '
4658
    UPDATE `marc_permissions`
4659
    SET
4660
      tagfield = ?,
4661
      tagsubfield = ?,
4662
      module = ?,
4663
      filter = ?,
4664
      on_existing = ?,
4665
      on_new = ?,
4666
      on_removed = ?
4667
    WHERE
4668
      id = ?
4669
    ';
4670
    my $sth = $dbh->prepare($query);
4671
    return $sth->execute (
4672
                      $f->{tagfield},
4673
                      $f->{tagsubfield},
4674
                      $f->{module},
4675
                      $f->{filter},
4676
                      $f->{on_existing},
4677
                      $f->{on_new},
4678
                      $f->{on_removed},
4679
                      $id
4680
                  );
4681
}
4682
4683
=head2 AddMarcPermissionsRule
4684
4685
    my $success = AddMarcPermissionsRule($fields)
4686
4687
Add rule to the marc_permissions table.
4688
4689
=over 4
4690
4691
=item C<$fields>
4692
4693
hashref defining the table fields
4694
4695
      tagfield - required
4696
      tagsubfield
4697
      module - required
4698
      filter - required
4699
      on_existing - required
4700
      on_new - required
4701
      on_removed - required
4702
4703
=back
4704
4705
Returns:
4706
4707
=over 4
4708
4709
=item C<$success>
4710
4711
undef if an error occurs, otherwise true.
4712
4713
=back
4714
4715
=cut
4716
4717
sub AddMarcPermissionsRule {
4718
    my $f = shift;
4719
    my $dbh = C4::Context->dbh;
4720
    my $query = '
4721
    INSERT INTO `marc_permissions`
4722
    (
4723
      tagfield,
4724
      tagsubfield,
4725
      module,
4726
      filter,
4727
      on_existing,
4728
      on_new,
4729
      on_removed
4730
    )
4731
    VALUES (?, ?, ?, ?, ?, ?, ?)
4732
    ';
4733
    my $sth = $dbh->prepare($query);
4734
    return $sth->execute (
4735
                      $f->{tagfield},
4736
                      $f->{tagsubfield},
4737
                      $f->{module},
4738
                      $f->{filter},
4739
                      $f->{on_existing},
4740
                      $f->{on_new},
4741
                      $f->{on_removed}
4742
                  );
4743
}
4744
4745
=head2 DelMarcPermissionsRule
4746
4747
    my $success = DelMarcPermissionsRule($id)
4748
4749
Deletes rule from the marc_permissions table.
4750
4751
=over 4
4752
4753
=item C<$id>
4754
4755
rule id to delete
4756
4757
=back
4758
4759
Returns:
4760
4761
=over 4
4762
4763
=item C<$success>
4764
4765
undef if an error occurs, otherwise true.
4766
4767
=back
4768
4769
=cut
4770
4771
sub DelMarcPermissionsRule {
4772
    my $id = shift;
4773
    my $dbh = C4::Context->dbh;
4774
    my $query = '
4775
    DELETE FROM `marc_permissions`
4776
    WHERE
4777
      id = ?
4778
    ';
4779
    my $sth = $dbh->prepare($query);
4780
    return $sth->execute($id);
4781
}
4782
3807
1;
4783
1;
3808
4784
3809
4785
(-)a/admin/marc-permissions.pl (+123 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 strict;
19
use warnings;
20
21
# standard or CPAN modules used
22
use CGI qw ( -utf8 );
23
use CGI::Cookie;
24
use MARC::File::USMARC;
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
38
my $script_name = "/cgi-bin/koha/admin/marc-permissions.pl";
39
40
my $input = new CGI;
41
my $op = $input->param('op') || '';
42
43
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
44
    {
45
        template_name   => "admin/marc-permissions.tt",
46
        query           => $input,
47
        type            => "intranet",
48
        authnotrequired => 0,
49
        flagsrequired   => { parameters => 'parameters_remaining_permissions' },
50
        debug           => 1,
51
    }
52
);
53
54
my %cookies = parse CGI::Cookie($cookie);
55
our $sessionID = $cookies{'CGISESSID'}->value;
56
57
my $rules;
58
if ( $op eq "remove" ) {
59
    $template->{VARS}->{removeConfirm} = 1;
60
    my @removeIDs = $input->multi_param('batchremove');
61
    push( @removeIDs, scalar $input->param('id') ) if $input->param('id');
62
63
    $rules = GetMarcPermissionsRules();
64
    for my $removeID (@removeIDs) {
65
        map { $_->{'remove'} = 1 if $_->{'id'} == $removeID } @{$rules};
66
    }
67
68
}
69
elsif ( $op eq "doremove" ) {
70
    my @removeIDs = $input->multi_param('batchremove');
71
    push( @removeIDs, scalar $input->param('id') ) if $input->param('id');
72
    for my $removeID (@removeIDs) {
73
        DelMarcPermissionsRule($removeID);
74
    }
75
76
    $rules = GetMarcPermissionsRules();
77
78
}
79
elsif ( $op eq "edit" ) {
80
    $template->{VARS}->{edit} = 1;
81
    my $id = $input->param('id');
82
    $rules = GetMarcPermissionsRules();
83
    map { $_->{'edit'} = 1 if $_->{'id'} == $id } @{$rules};
84
85
}
86
elsif ( $op eq "doedit" ) {
87
    my $id     = $input->param('id');
88
    my $fields = {
89
        module      => scalar $input->param('module'),
90
        tagfield    => scalar $input->param('tagfield'),
91
        tagsubfield => scalar $input->param('tagsubfield'),
92
        filter      => scalar $input->param('filter'),
93
        on_existing => scalar $input->param('on_existing'),
94
        on_new      => scalar $input->param('on_new'),
95
        on_removed  => scalar $input->param('on_removed')
96
    };
97
98
    ModMarcPermissionsRule( $id, $fields );
99
    $rules = GetMarcPermissionsRules();
100
101
}
102
elsif ( $op eq "add" ) {
103
    my $fields = {
104
        module      => scalar $input->param('module'),
105
        tagfield    => scalar $input->param('tagfield'),
106
        tagsubfield => scalar $input->param('tagsubfield'),
107
        filter      => scalar $input->param('filter'),
108
        on_existing => scalar $input->param('on_existing'),
109
        on_new      => scalar $input->param('on_new'),
110
        on_removed  => scalar $input->param('on_removed')
111
    };
112
113
    AddMarcPermissionsRule($fields);
114
    $rules = GetMarcPermissionsRules();
115
116
}
117
else {
118
    $rules = GetMarcPermissionsRules();
119
}
120
my $modules = GetMarcPermissionsModules();
121
$template->param( rules => $rules, modules => $modules );
122
123
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+2 lines)
Lines 86-91 Link Here
86
    <dd>Manage OAI Sets</dd>
86
    <dd>Manage OAI Sets</dd>
87
    <dt><a href="/cgi-bin/koha/admin/items_search_fields.pl">Items search fields</a></dt>
87
    <dt><a href="/cgi-bin/koha/admin/items_search_fields.pl">Items search fields</a></dt>
88
    <dd>Manage custom fields for items search</dd>
88
    <dd>Manage custom fields for items search</dd>
89
    <dt><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></dt>
90
    <dd>Managed MARC field permissions</dd>
89
</dl>
91
</dl>
90
92
91
<h3>Acquisition parameters</h3>
93
<h3>Acquisition parameters</h3>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-permissions.tt (+485 lines)
Line 0 Link Here
1
[% USE Koha %]
2
[% INCLUDE 'doc-head-open.inc' %]
3
<title>Koha &rsaquo; Administration &rsaquo; MARC field permissions</title>
4
[% INCLUDE 'doc-head-close.inc' %]
5
<link rel="stylesheet" type="text/css" href="[% themelang %]/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
//<![CDATA[
16
17
function doSubmit(op, id) {
18
    $('<input type="hidden"/>')
19
    .attr('name', 'op')
20
    .attr('value', op)
21
    .appendTo('#marc-permissions-form');
22
23
    if(id) {
24
        $('<input type="hidden"/>')
25
        .attr('name', 'id')
26
        .attr('value', id)
27
        .appendTo('#marc-permissions-form');
28
    }
29
30
    var valid = true;
31
    if( op == 'add' || op == 'edit') {
32
        var validate = [
33
                        $('#marc-permissions-form input[name="filter"]'),
34
                        $('#marc-permissions-form input[name="tagfield"]')
35
                       ];
36
        for(var i=0; i < validate.length; i++) {
37
            if(validate[i].val().length == 0) {
38
                validate[i].addClass('required');
39
                valid = false;
40
            } else {
41
                validate[i].removeClass('required');
42
            }
43
        }
44
    }
45
46
    if(valid ) {
47
        $('#marc-permissions-form').submit();
48
    }
49
50
    return valid;
51
}
52
53
$(document).ready(function(){
54
    $('#btn-help').click(function() {
55
        $('#help').toggle();
56
    });
57
58
    /* disable some options if subfield is indicator */
59
    $('input[name="tagsubfield"]').change(function() {
60
        if( /i[0-9]/.test($(this).val()) ) {
61
            $('select[name="on_existing"] option[value="add"]').attr('disabled', 'disabled')
62
            $('select[name="on_existing"] option[value="add_or_correct"]').attr('disabled', 'disabled')
63
        } else {
64
            $('select[name="on_existing"] option[value="add"]').removeAttr('disabled')
65
            $('select[name="on_existing"] option[value="add_or_correct"]').removeAttr('disabled')
66
        }
67
    });
68
69
    /* disable batch remove unless one or more checkboxes are checked */
70
    $('input[name="batchremove"]').change(function() {
71
        if($('input[name="batchremove"]:checked').length > 0) {
72
            $('#btn_batchremove').removeAttr('disabled');
73
        } else {
74
            $('#btn_batchremove').attr('disabled', 'disabled');
75
        }
76
    });
77
78
    /* check validity of regexes in field and subfield inputs */
79
    $('input[name="tagfield"], input[name="tagsubfield"]').change(function() {
80
        $(this).removeClass('required');
81
        $(this).attr('title', '');
82
        if($(this).val() != '*') {
83
            try {
84
                new RegExp($(this).val())
85
            } catch ( e ) {
86
                $(this).addClass('required');
87
                $(this).attr('title', 'Invalid regular expression: ' + e.message);
88
            }
89
        }
90
    });
91
92
    $.fn.dataTable.ext.order['dom-input'] = function (settings, col) {
93
        return this.api().column(col, { order: 'index' }).nodes()
94
            .map(function (td, i) {
95
                if($('input', td).val() != undefined) {
96
                    return $('input', td).val();
97
                } else if($('select', td).val() != undefined) {
98
                    return $('option[selected="selected"]', td).val();
99
                } else {
100
                    return $(td).html();
101
                }
102
            });
103
    }
104
105
    $('#marc-permissions').dataTable($.extend(true, {}, dataTablesDefaults, {
106
        "aoColumns": [
107
            {"bSearchable": false, "bSortable": false},
108
            {"sSortDataType": "dom-input"},
109
            {"sSortDataType": "dom-input"},
110
            {"bSearchable": false, "sSortDataType": "dom-input"},
111
            {"bSearchable": false, "sSortDataType": "dom-input"},
112
            {"bSearchable": false, "sSortDataType": "dom-input"},
113
            {"bSearchable": false, "sSortDataType": "dom-input"},
114
            {"bSearchable": false, "sSortDataType": "dom-input"},
115
            {"bSearchable": false, "bSortable": false},
116
            {"bSearchable": false, "bSortable": false}
117
        ],
118
        "sPaginationType": "four_button"
119
    } ));
120
121
});
122
//]]>
123
</script>
124
</head>
125
<body id="admin_marc-permissions" class="admin">
126
[% INCLUDE 'header.inc' %]
127
[% INCLUDE 'cat-search.inc' %]
128
129
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a>
130
 &rsaquo; MARC field permissions
131
</div>
132
133
<div id="doc3" class="yui-t2">
134
   <div id="bd">
135
    <div id="yui-main">
136
    <div class="yui-b">
137
138
<h1>Manage MARC field permissions <a href="#" id="btn-help"><button type="button" class="btn btn-small"><i class="icon-question-sign"></i></button></a></h1>
139
[% UNLESS Koha.Preference( 'MARCPermissions' ) %]
140
<div class="dialog message">
141
The <b>MARCPermissions</b> preference is not set, don't forget to enable it for rules to take effect.
142
</div>
143
[% END %]
144
<div id="help" style="display:none;" class="well">
145
    <h3>Rule evaluation</h3>
146
    <p>
147
    Rules are evaluated from least specific to most specific. This means that a
148
    more specific rule will override a less specific rule.
149
150
    <b>*</b> is less specific than a regular expression. A regular expression is
151
    less specific than a normal field name (e.g. <b>245</b>).
152
153
    A <i>Subfield tag</i> is more specific than a <i>Tag</i>.
154
155
    <p>
156
    To add a field-specific rule (i.e. not a subfield), leave the <i>Subfield
157
    tag</i> field blank.
158
    </p>
159
    </p>
160
    <h4>Defaults</h4>
161
    <p>
162
    Default action when no rules exist is to replace the old record with the new
163
    record. Same as if <b>MARCPermissions</b> is disabled.
164
    </p>
165
    <p>
166
    Default action when no matching rule is found is to leave a field
167
    unchanged (<b>skip</b>). If you wish to changed the default actions for
168
    fields and subfields, please add wildcard rules.
169
    </p>
170
171
    <h4>Wildcards</h4>
172
    <p>
173
    <b>*</b> can be used as a wildcard for <i>Tag</i>, <i>Subfield tag</i> and
174
    <i>Filter</i>. <b>*</b> is considered less specific than a non wildcard value,
175
    and thus will be overridden by a non wildcard value (e.g. "100", "a", etc).
176
    </p>
177
178
    <h4>Regular expressions</h4>
179
    Regular expressions can be used in both the <i>Tag</i> and <i>Subfield
180
    tag</i> fields. Beware though, using regular expressions may create
181
    overlapping matches, in which case they will be applied in a sorted order.
182
183
    <h4>Example rules</h4>
184
    <p>
185
    Following is an example rule set in order of specificity.
186
    </p>
187
    <table>
188
        <thead><tr>
189
            <th>Tag</th>
190
            <th>Subfield tag</th>
191
            <th>Module</th>
192
            <th>Filter</th>
193
            <th>Description</th>
194
        </tr></thead>
195
        <tbody>
196
        <tr>
197
            <td>*</td>
198
            <td></td>
199
            <td>[% modules.0.name %]</td>
200
            <td>*</td>
201
            <td><i>Match any field regardless of [% modules.0.name %]</i></td>
202
        </tr>
203
        <tr>
204
            <td>*</td>
205
            <td>*</td>
206
            <td>[% modules.0.name %]</td>
207
            <td>*</td>
208
            <td><i>Match any subfield regardless of [% modules.0.name %]</i></td>
209
        </tr>
210
        <tr>
211
            <td>245</td>
212
            <td>*</td>
213
            <td>[% modules.0.name %]</td>
214
            <td>z39.50</td>
215
            <td><i>Match any subfield under <b>245</b> when [% modules.0.name %]
216
            is <b>z39.50</b></i></td>
217
        </tr>
218
        <tr>
219
            <td>*</td>
220
            <td>b</td>
221
            <td>[% modules.0.name %]</td>
222
            <td>z39.50</td>
223
            <td><i>Match subfield <b>b</b> regardless of field when [% modules.0.name %]
224
            is <b>z39.50</b></i></td>
225
        </tr>
226
        <tr>
227
            <td>500</td>
228
            <td>[a-d]</td>
229
            <td>[% modules.0.name %]</td>
230
            <td>z39.50</td>
231
            <td><i>Match subfields <b>a, b, c, d</b> when field is 500 and [%
232
            modules.0.name %] is <b>z39.50</b></i></td>
233
        </tr>
234
        <tr>
235
            <td>5..</td>
236
            <td>a</td>
237
            <td>[% modules.0.name %]</td>
238
            <td>z39.50</td>
239
            <td><i>Match subfield <b>a</b> when field matches the regular
240
            expression <b>5..</b> (500-599) and [% modules.0.name %] is <b>z39.50</b></i></td>
241
        </tr>
242
        <tr>
243
            <td>245</td>
244
            <td>a</td>
245
            <td>[% modules.0.name %]</td>
246
            <td>z39.50</td>
247
            <td><i>Match subfield <b>a</b> when field is <b>500</b> and [%
248
            modules.0.name %] is <b>z39.50</b></i></td>
249
        </tr>
250
        </tbody>
251
    </table>
252
253
    <h3>Available filter modules</h3>
254
    Filters cannot be regular expressions. Please use a single <b>*</b> as a
255
    wildcard if need.
256
    <table>
257
        <thead>
258
            <tr>
259
                <th>Specificity</th>
260
                <th>Module</th>
261
                <th>Description</th>
262
            </tr>
263
        </thead>
264
        <tbody>
265
        [% FOREACH module IN modules %]
266
            <tr>
267
                <td>[% module.specificity %]</td>
268
                <td>[% module.name %]</td>
269
                <td>[% module.description %]</td>
270
            </tr>
271
        [% END %]
272
        </tbody>
273
    </table>
274
275
    <h3>Available sources</h3>
276
    The following sources currently implement MARCPermissions.
277
278
    <table>
279
        <thead>
280
            <tr>
281
                <th>Name</th>
282
                <th>Description</th>
283
            </tr>
284
        </thead>
285
        <tbody>
286
            <tr>
287
                <td>bulkmarcimport</td>
288
                <td>bin/migration_tools/bulkmarcimport.pl</td>
289
            </tr>
290
            <tr>
291
                <td>import_lexile</td>
292
                <td>bin/migration_tools/import_lexile.pl</td>
293
            </tr>
294
            <tr>
295
                <td>z39.50</td>
296
                <td>Import from Z39.50 search in browser</td>
297
            </tr>
298
            <tr>
299
                <td>intranet</td>
300
                <td>Modifications from intranet in browser</td>
301
            </tr>
302
            <tr>
303
                <td>batchmod</td>
304
                <td>Batch record modification in browser</td>
305
            </tr>
306
        </tbody>
307
    </table>
308
309
310
311
    <h3>Indicators</h3>
312
    <p>
313
    Indicators can be addressed as <b>i1</b> and <b>i2</b> in the <i>Subfield
314
    tag</i> field. Some actions might not be available for indicators, in which
315
    case they will be disabled.
316
    </p>
317
318
    <h3>Logging</h3>
319
    <p>If <b>MARCPermissionsLog</b> is enabled, log entries for each record
320
    modification will be available in the <b>Modification log</b> in the
321
    <b>Catalog</b> module under the <b>Modify</b> action. This can be very
322
    helpful when debugging rule sets.</p>
323
</div>
324
325
[% IF removeConfirm %]
326
<div class="dialog alert">
327
<h3>Remove rule?</h3>
328
<p>Are you sure you want to remove the selected rule(s)?</p>
329
330
<form action="[% script_name %]" method="GET">
331
    <input type="submit" value="No, do not remove" class="deny"/>
332
</form>
333
<input type="button" value="Yes, remove" class="approve" onClick="doSubmit('doremove');"/>
334
</div>
335
[% END %]
336
337
<form action="[% script_name %]" method="POST" id="marc-permissions-form">
338
<table id="marc-permissions">
339
    <thead><tr>
340
        <th>Rule</th>
341
        <th>Tag</th>
342
        <th>Subfield tag</th>
343
        <th>Module</th>
344
        <th>Filter</th>
345
        <th>On Existing</th>
346
        <th>On New</th>
347
        <th>On Removed</th>
348
        <th>&nbsp;</th>
349
        <th>&nbsp;</th>
350
    </tr></thead>
351
    [% UNLESS edit %]
352
    <tfoot>
353
        <tr>
354
            <th>&nbsp;</th>
355
            <th><input type="text" size="5" name="tagfield"/></th>
356
            <th><input type="text" size="5" name="tagsubfield"/></th>
357
            <th>
358
                <select name="module">
359
                    [% FOREACH module IN modules %]
360
                    <option value="[% module.id %]">[% module.name %]</option>
361
                    [% END %]
362
                </select>
363
            </th>
364
            <th><input type="text" size="5" name="filter"/></th>
365
            <th>
366
                <select name="on_existing">
367
                [% FOR on_ex IN ['skip', 'overwrite', 'add', 'add_or_correct'] %]
368
                    <option value="[% on_ex %]">[% on_ex %]</option>
369
                [% END %]
370
                </select>
371
            </th>
372
            <th>
373
                <select name="on_new">
374
                [% FOR on_new IN ['skip', 'add'] %]
375
                    <option value="[% on_new %]">[% on_new %]</option>
376
                [% END %]
377
                </select>
378
            </th>
379
            <th>
380
                <select name="on_removed">
381
                [% FOR on_removed IN ['skip', 'remove'] %]
382
                    <option value="[% on_removed %]">[% on_removed %]</option>
383
                [% END %]
384
                </select>
385
            </th>
386
            <th><button class="btn btn-small" type="button" title="Add" onClick="doSubmit('add');" ><i class="icon-plus"></i></button></th>
387
            <th><button id="btn_batchremove" disabled="disabled" class="btn btn-small" type="button" title="Batch remove" onClick="doSubmit('remove');" ><i class="icon-trash"></i></button></th>
388
        </tr>
389
    </tfoot>
390
    [% END %]
391
    <tbody>
392
        [% FOREACH rule IN rules %]
393
            <tr id="[% rule.id %]">
394
            [% IF rule.edit %]
395
                <td>[% rule.id %]</td>
396
                <td><input type="text" size="3" name="tagfield" value="[% rule.tagfield %]"/></td>
397
                <td><input type="text" size="1" name="tagsubfield" value="[% rule.tagsubfield %]"/></td>
398
                <td>
399
                    <select name="module">
400
                        [% FOREACH module IN modules %]
401
                            [% IF module.name == rule.module %]
402
                                <option value="[% module.id %]" selected="selected">[% module.name %]</option>
403
                            [% ELSE %]
404
                                <option value="[% module.id %]">[% module.name %]</option>
405
                            [% END %]
406
                        [% END %]
407
                    </select>
408
                </td>
409
                <td><input type="text" size="5" name="filter" value="[% rule.filter %]"/></td>
410
                <td>
411
                    <select name="on_existing">
412
                        [% FOR on_ex IN ['skip', 'overwrite', 'add', 'add_or_correct'] %]
413
                            [% IF on_ex == rule.on_existing %]
414
                                <option value="[% on_ex %]" selected="selected">[% on_ex %]</option>
415
                            [% ELSE %]
416
                                <option value="[% on_ex %]">[% on_ex %]</option>
417
                            [% END %]
418
                        [% END %]
419
                    </select>
420
                </td>
421
                <td>
422
                    <select name="on_new">
423
                        [% FOR on_new IN ['skip', 'add'] %]
424
                            [% IF on_new == rule.on_new %]
425
                                <option value="[% on_new %]" selected="selected">[% on_new %]</option>
426
                            [% ELSE %]
427
                                <option value="[% on_new %]">[% on_new %]</option>
428
                            [% END %]
429
                        [% END %]
430
                    </select>
431
                </td>
432
                <td>
433
                    <select name="on_removed">
434
                        [% FOR on_removed IN ['skip', 'remove'] %]
435
                            [% IF on_removed == rule.on_removed %]
436
                                <option value="[% on_removed %]" selected="selected">[% on_removed %]</option>
437
                            [% ELSE %]
438
                                <option value="[% on_removed %]">[% on_removed %]</option>
439
                            [% END %]
440
                        [% END %]
441
                    </select>
442
                </td>
443
                <td>
444
                    <button class="btn btn-small" type="button" title="Save" onClick="doSubmit('doedit', [% rule.id %]);"><i class="icon-ok"></i></button>
445
                    <a href="?"><button class="btn btn-small" type="button" title="Cancel" ><i class="icon-ban-circle"></i></button></a>
446
                    </td>
447
                <td></td>
448
            [% ELSE %]
449
                <td>[% rule.id %]</td>
450
                <td>[% rule.tagfield %]</td>
451
                <td>[% rule.tagsubfield%]</td>
452
                <td>[% rule.module %]</td>
453
                <td>[% rule.filter %]</td>
454
                <td>[% rule.on_existing %]</td>
455
                <td>[% rule.on_new %]</td>
456
                <td>[% rule.on_removed %]</td>
457
                <td>
458
                    <a href="?op=remove&id=[% rule.id %]" title="Remove"><i class="icon-remove"></i></a>
459
                    <a href="?op=edit&id=[% rule.id %]" title="Edit"><i class="icon-pencil"></i></a>
460
                </td>
461
                <td>
462
                    [% IF rule.remove %]
463
                    <input type="checkbox" name="batchremove" value="[% rule.id %]" checked="checked"/>
464
                    [% ELSE %]
465
                    <input type="checkbox" name="batchremove" value="[% rule.id %]"/>
466
                    [% END %]
467
                </td>
468
            [% END %]
469
            </tr>
470
        [% END %]
471
    </tbody>
472
</table>
473
</form>
474
475
<form action="[% script_name %]" method="post">
476
<input type="hidden" name="op" value="redo-matching" />
477
</form>
478
479
</div>
480
</div>
481
<div class="yui-b">
482
[% INCLUDE 'admin-menu.inc' %]
483
</div>
484
</div>
485
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref (-22 / +9 lines)
Lines 215-240 Cataloging: Link Here
215
                  yes: "do"
215
                  yes: "do"
216
                  no: "don't"
216
                  no: "don't"
217
            - attempt to match aggressively by trying all variations of the ISBNs in the imported record as a phrase in the ISBN fields of already cataloged records.  Note that this preference has no effect if UseQueryParser is on.
217
            - attempt to match aggressively by trying all variations of the ISBNs in the imported record as a phrase in the ISBN fields of already cataloged records.  Note that this preference has no effect if UseQueryParser is on.
218
    Exporting:
219
        -
218
        -
220
            - Include following fields when exporting BibTeX,
219
            - When importing records
221
            - pref: BibtexExportAdditionalFields
220
            - pref: MARCPermissions
222
              type: textarea
221
              choices:
223
            - "Use one line per tag in the format BT_TAG: TAG$SUBFIELD ( e.g. lccn: 010$a )"
222
                  yes: "use"
224
            - "<br/>"
223
                  no: "don't use"
225
            - "To specificy multiple marc tags/subfields as targets for a repeating BibTex tag, use the following format: BT_TAG: [TAG2$SUBFIELD1, TAG2$SUBFIELD2] ( e.g. notes: [501$a, 505$g] )"
224
            - MARC permissions rules to decide which action to take for each field.
226
            - "<br/>"
225
            - When <b>add_or_correct</b>, consider a field modification to be a correction if the old and new value has a similarity of
227
            - "All values of repeating tags and subfields will be printed with the given BibTeX tag."
226
            - pref: MARCPermissionsCorrectionSimilarity
228
            - "<br/>"
227
            - %. If similarity falls below this value, the new value will be treated as a new field.
229
            - "Use '@' ( with quotes ) as the BT_TAG to replace the bibtex record type with a field value of your choosing."
230
        -
231
            - Include following fields when exporting RIS,
232
            - pref: RisExportAdditionalFields
233
              type: textarea
234
            - "Use one line per tag in the format RIS_TAG: TAG$SUBFIELD ( e.g. LC: 010$a )"
235
            - "<br/>"
236
            - "To specificy multiple marc tags/subfields as targets for a repeating RIS tag, use the following format: RIS_TAG: [TAG2$SUBFIELD1, TAG2$SUBFIELD2] ( e.g. NT: [501$a, 505$g] )"
237
            - "<br/>"
238
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
239
            - "<br/>"
240
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/logs.pref (+6 lines)
Lines 60-65 Logging: Link Here
60
                  on: Log
60
                  on: Log
61
                  off: "Don't log"
61
                  off: "Don't log"
62
            - when reports are added, deleted or changed.
62
            - when reports are added, deleted or changed.
63
        -
64
            - pref: MARCPermissionsLog
65
              choices:
66
                  on: Log
67
                  off: "Don't log"
68
            - MARC permissions rule evaluations when applying rules for MARC field modifications.
63
    Debugging:
69
    Debugging:
64
        -
70
        -
65
            - pref: DumpTemplateVarsIntranet
71
            - pref: DumpTemplateVarsIntranet
(-)a/t/db_dependent/Biblio/MARCPermissions.t (-1 / +284 lines)
Line 0 Link Here
0
- 
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
use Test::More tests => 16;
21
use Test::MockModule;
22
23
use MARC::Record;
24
use Data::Dumper;
25
26
BEGIN {
27
    use_ok('C4::Biblio');
28
}
29
30
# Start transaction
31
my $dbh = C4::Context->dbh;
32
$dbh->{AutoCommit} = 0;
33
$dbh->{RaiseError} = 1;
34
35
# Undef C4::Biblio::inverted_field_map to avoid problems introduced
36
# by caching in TransformMarcToKoha
37
undef $C4::Biblio::inverted_field_map;
38
39
C4::Context->set_preference( "MARCPermissions", 1);
40
C4::Context->set_preference( "MARCPermissionsLog", 1);
41
C4::Context->set_preference( "MARCPermissionsCorrectionSimilarity", 90);
42
43
# Create a record
44
my $record = MARC::Record->new();
45
$record->append_fields (
46
    MARC::Field->new('008', '12345'),
47
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
48
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
49
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
50
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
51
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
52
);
53
54
# Add record to DB
55
my ($biblionumber, $biblioitemnumber) = AddBiblio($record, '');
56
57
my $modules = GetMarcPermissionsModules();
58
59
##############################################################################
60
# Test overwrite rule
61
my $mod_record = MARC::Record->new();
62
$mod_record->append_fields (
63
    MARC::Field->new('008', '12345'),
64
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
65
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
66
    MARC::Field->new('500', '1','1', 'a' => 'this field has now been changed'),
67
    MARC::Field->new('500', '2','2', 'a' => 'and so have this field'),
68
);
69
70
# Clear MARC permission rules from DB
71
DelMarcPermissionsRule($_->{id}) for GetMarcPermissionsRules();
72
73
# Add MARC permission rules to DB
74
AddMarcPermissionsRule({
75
    module => $modules->[0]->{'id'},
76
    tagfield => '*',
77
    tagsubfield => '',
78
    filter => '*',
79
    on_existing => 'overwrite',
80
    on_new => 'add',
81
    on_removed => 'remove'
82
});
83
84
my @log = ();
85
my $new_record = ApplyMarcPermissions({
86
        biblionumber => $biblionumber,
87
        record => $mod_record,
88
        frameworkcode => '',
89
        filter => {$modules->[0]->{'name'} => 'foo'},
90
        log => \@log
91
    });
92
93
my @a500 = $new_record->field('500');
94
is ($a500[0]->subfield('a'), 'this field has now been changed', 'old field is replaced when overwrite');
95
is ($a500[1]->subfield('a'), 'and so have this field', 'old field is replaced when overwrite');
96
97
##############################################################################
98
# Test remove rule
99
is ($new_record->field('250'), undef, 'removed field is removed');
100
101
##############################################################################
102
# Test skip rule
103
$mod_record = MARC::Record->new();
104
$mod_record->append_fields (
105
    MARC::Field->new('008', '12345'),
106
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
107
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
108
    MARC::Field->new('500', '1','1', 'a' => 'this should not show'),
109
    MARC::Field->new('500', '2','2', 'a' => 'and neither should this'),
110
);
111
112
AddMarcPermissionsRule({
113
    module => $modules->[0]->{'id'},
114
    tagfield => '500',
115
    tagsubfield => '*',
116
    filter => '*',
117
    on_existing => 'skip',
118
    on_new => 'skip',
119
    on_removed => 'skip'
120
});
121
122
@log = ();
123
$new_record = ApplyMarcPermissions({
124
        biblionumber => $biblionumber,
125
        record => $mod_record,
126
        frameworkcode => '',
127
        filter => {$modules->[0]->{'name'} => 'foo'},
128
        log => \@log
129
    });
130
131
@a500 = $new_record->field('500');
132
is ($a500[0]->subfield('a'), 'the lazy programmer jumps over the quick brown tests', 'old field is kept when skip');
133
is ($a500[1]->subfield('a'), 'the quick brown test jumps over the lazy programmers', 'old field is kept when skip');
134
135
##############################################################################
136
# Test add rule
137
$mod_record = MARC::Record->new();
138
$mod_record->append_fields (
139
    MARC::Field->new('008', '12345'),
140
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
141
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
142
    #MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
143
    MARC::Field->new('245', '1','2', 'a' => 'some new fun value'),
144
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
145
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
146
);
147
148
AddMarcPermissionsRule({
149
    module => $modules->[0]->{'id'},
150
    tagfield => '245',
151
    tagsubfield => '*',
152
    filter => '*',
153
    on_existing => 'add',
154
    on_new => 'add',
155
    on_removed => 'skip'
156
});
157
158
159
@log = ();
160
$new_record = ApplyMarcPermissions({
161
        biblionumber => $biblionumber,
162
        record => $mod_record,
163
        frameworkcode => '',
164
        filter => {$modules->[0]->{'name'} => 'foo'},
165
        log => \@log
166
    });
167
168
my @a245 = $new_record->field('245')->subfield('a');
169
is ($a245[0], 'field data for 245 a with indicators 12', 'old field is kept when adding new');
170
is ($a245[1], 'some new fun value', 'new field is added');
171
172
##############################################################################
173
# Test add_or_correct rule
174
$mod_record = MARC::Record->new();
175
$mod_record->append_fields (
176
    MARC::Field->new('008', '12345'),
177
    #MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
178
    MARC::Field->new('100', '','', 'a' => 'a very different value'),
179
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
180
    #MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
181
    MARC::Field->new('245', '1','2', 'a' => 'Field data for 245 a with indicators 12', 'a' => 'some very different value'),
182
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
183
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
184
);
185
186
AddMarcPermissionsRule({
187
    module => $modules->[0]->{'id'},
188
    tagfield => '(100|245)',
189
    tagsubfield => '*',
190
    filter => '*',
191
    on_existing => 'add_or_correct',
192
    on_new => 'add',
193
    on_removed => 'skip'
194
});
195
196
@log = ();
197
$new_record = ApplyMarcPermissions({
198
        biblionumber => $biblionumber,
199
        record => $mod_record,
200
        frameworkcode => '',
201
        filter => {$modules->[0]->{'name'} => 'foo'},
202
        log => \@log
203
    });
204
205
@a245 = $new_record->field('245')->subfield('a');
206
is ($a245[0], 'Field data for 245 a with indicators 12', 'add_or_correct modifies field when a correction');
207
is ($a245[1], 'some very different value', 'add_or_correct adds field when not a correction');
208
209
my @a100 = $new_record->field('100')->subfield('a');
210
is ($a100[0], 'field data for 100 a without indicators', 'add_or_correct keeps old field when not a correction');
211
is ($a100[1], 'a very different value', 'add_or_correct adds field when not a correction');
212
213
##############################################################################
214
# Test rule evaluation order
215
$mod_record = MARC::Record->new();
216
$mod_record->append_fields (
217
    MARC::Field->new('008', '12345'),
218
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
219
    MARC::Field->new('250', '','', 'a' => 'take one down, pass it around'),
220
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
221
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
222
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
223
);
224
225
226
DelMarcPermissionsRule($_->{id}) for GetMarcPermissionsRules();
227
228
AddMarcPermissionsRule({
229
    module => $modules->[0]->{'id'},
230
    tagfield => '*',
231
    tagsubfield => '*',
232
    filter => '*',
233
    on_existing => 'skip',
234
    on_new => 'skip',
235
    on_removed => 'skip'
236
});
237
AddMarcPermissionsRule({
238
    module => $modules->[0]->{'id'},
239
    tagfield => '250',
240
    tagsubfield => '*',
241
    filter => '*',
242
    on_existing => 'overwrite',
243
    on_new => 'skip',
244
    on_removed => 'skip'
245
});
246
AddMarcPermissionsRule({
247
    module => $modules->[0]->{'id'},
248
    tagfield => '*',
249
    tagsubfield => 'a',
250
    filter => '*',
251
    on_existing => 'add_or_correct',
252
    on_new => 'skip',
253
    on_removed => 'skip'
254
});
255
AddMarcPermissionsRule({
256
    module => $modules->[0]->{'id'},
257
    tagfield => '250',
258
    tagsubfield => 'a',
259
    filter => '*',
260
    on_existing => 'add',
261
    on_new => 'skip',
262
    on_removed => 'skip'
263
});
264
265
@log = ();
266
$new_record = ApplyMarcPermissions({
267
        biblionumber => $biblionumber,
268
        record => $mod_record,
269
        frameworkcode => '',
270
        filter => {$modules->[0]->{'name'} => 'foo'},
271
        log => \@log
272
    });
273
274
my @rule = grep { $_->{tag} eq '250' and $_->{subfieldcode} eq 'a' } @log;
275
is(scalar @rule, 1, 'only one rule applied');
276
is($rule[0]->{event}.':'.$rule[0]->{action}, 'existing:add', 'most specific rule used');
277
278
my @a250 = $new_record->field('250')->subfield('a');
279
is ($a250[0], '250 bottles of beer on the wall', 'most specific rule is applied, original field kept');
280
is ($a250[1], 'take one down, pass it around', 'most specific rule is applied, new field added');
281
282
$dbh->rollback;
283
284
1;

Return to bug 14957