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

(-)a/C4/Biblio.pm (-22 / +998 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 105-110 BEGIN { Link Here
105
      &CountBiblioInOrders
106
      &CountBiblioInOrders
106
      &GetSubscriptionsId
107
      &GetSubscriptionsId
107
      &GetHolds
108
      &GetHolds
109
110
      &GetMarcPermissionsRules
111
      &GetMarcPermissionsModules
112
      &ModMarcPermissionsRule
113
      &AddMarcPermissionsRule
114
      &DelMarcPermissionsRule
108
    );
115
    );
109
116
110
    # To modify something
117
    # To modify something
Lines 133-138 BEGIN { Link Here
133
    # they are useful in a few circumstances, so they are exported,
140
    # they are useful in a few circumstances, so they are exported,
134
    # but don't use them unless you are a core developer ;-)
141
    # but don't use them unless you are a core developer ;-)
135
    push @EXPORT, qw(
142
    push @EXPORT, qw(
143
      &ApplyMarcPermissions
136
      &ModBiblioMarc
144
      &ModBiblioMarc
137
    );
145
    );
138
146
Lines 230-244 bib to add, while the second argument is the desired MARC Link Here
230
framework code.
238
framework code.
231
239
232
This function also accepts a third, optional argument: a hashref
240
This function also accepts a third, optional argument: a hashref
233
to additional options.  The only defined option is C<defer_marc_save>,
241
to additional options. Defined options are:
234
which if present and mapped to a true value, causes C<AddBiblio>
242
235
to omit the call to save the MARC in C<bibilioitems.marc>
243
C<defer_marc_save>
236
and C<biblioitems.marcxml>  This option is provided B<only>
244
237
for the use of scripts such as C<bulkmarcimport.pl> that may need
245
    If present and mapped to a true value, causes C<AddBiblio> to omit the call
238
to do some manipulation of the MARC record for item parsing before
246
    to save the MARC in C<bibilioitems.marc> and C<biblioitems.marcxml>  This
239
saving it and which cannot afford the performance hit of saving
247
    option is provided B<only> for the use of scripts such as
240
the MARC record twice.  Consequently, do not use that option
248
    C<bulkmarcimport.pl> that may need to do some manipulation of the MARC
241
unless you can guarantee that C<ModBiblioMarc> will be called.
249
    record for item parsing before saving it and which cannot afford the
250
    performance hit of saving the MARC record twice.  Consequently, do not use
251
    that option unless you can guarantee that C<ModBiblioMarc> will be called.
252
253
C<filter>
254
255
    Optional hashref defining permission filters from the
256
    marc_permissions_modules table in form of {module => filter}. Three
257
    predefined filter modules exists:
258
259
        * source
260
        * category
261
        * borrower
242
262
243
=cut
263
=cut
244
264
Lines 271-277 sub AddBiblio { Link Here
271
    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
291
    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
272
292
273
    # now add the record
293
    # now add the record
274
    ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
294
    ModBiblioMarc( $record, $biblionumber, $frameworkcode, $options->{filter} ) unless $defer_marc_save;
275
295
276
    # update OAI-PMH sets
296
    # update OAI-PMH sets
277
    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
297
    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
Lines 300-311 in the C<biblio> and C<biblioitems> tables, as well as Link Here
300
which fields are used to store embedded item, biblioitem,
320
which fields are used to store embedded item, biblioitem,
301
and biblionumber data for indexing.
321
and biblionumber data for indexing.
302
322
323
This function also accepts a forth, optional argument: a hashref defining
324
permission filters from the marc_permissions_modules table in form of
325
{module => filter}. Three predefined filter modules exists:
326
327
    * source
328
    * category
329
    * borrower
330
303
Returns 1 on success 0 on failure
331
Returns 1 on success 0 on failure
304
332
305
=cut
333
=cut
306
334
307
sub ModBiblio {
335
sub ModBiblio {
308
    my ( $record, $biblionumber, $frameworkcode ) = @_;
336
    my ( $record, $biblionumber, $frameworkcode, $filter ) = @_;
337
309
    if (!$record) {
338
    if (!$record) {
310
        carp 'No record passed to ModBiblio';
339
        carp 'No record passed to ModBiblio';
311
        return 0;
340
        return 0;
Lines 343-355 sub ModBiblio { Link Here
343
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
372
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
344
373
345
    # load the koha-table data object
374
    # load the koha-table data object
346
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
375
    my $oldbiblio = TransformMarcToKoha( $dbh, $record, $frameworkcode, undef, $biblionumber, $filter );
347
376
348
    # update MARC subfield that stores biblioitems.cn_sort
377
    # update MARC subfield that stores biblioitems.cn_sort
349
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
378
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
350
379
351
    # update the MARC record (that now contains biblio and items) with the new record data
380
    # update the MARC record (that now contains biblio and items) with the new record data
352
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
381
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode, $filter);
353
382
354
    # modify the other koha tables
383
    # modify the other koha tables
355
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
384
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 1255-1261 $frameworkcode is optional. If not given, then the default framework is used. Link Here
1255
sub GetMarcSubfieldStructureFromKohaField {
1284
sub GetMarcSubfieldStructureFromKohaField {
1256
    my ($kohafield, $frameworkcode) = @_;
1285
    my ($kohafield, $frameworkcode) = @_;
1257
1286
1258
    return undef unless $kohafield;
1287
    return unless $kohafield;
1259
    $frameworkcode //= '';
1288
    $frameworkcode //= '';
1260
1289
1261
    my $dbh = C4::Context->dbh;
1290
    my $dbh = C4::Context->dbh;
Lines 2611-2617 our $inverted_field_map; Link Here
2611
2640
2612
=head2 TransformMarcToKoha
2641
=head2 TransformMarcToKoha
2613
2642
2614
  $result = TransformMarcToKoha( $record, $frameworkcode )
2643
  $result = TransformMarcToKoha( $record, $frameworkcode, $biblionumber, $filter )
2615
2644
2616
Extract data from a MARC bib record into a hashref representing
2645
Extract data from a MARC bib record into a hashref representing
2617
Koha biblio, biblioitems, and items fields. 
2646
Koha biblio, biblioitems, and items fields. 
Lines 2622-2628 hash_ref Link Here
2622
=cut
2651
=cut
2623
2652
2624
sub TransformMarcToKoha {
2653
sub TransformMarcToKoha {
2625
    my ( $record, $frameworkcode, $limit_table ) = @_;
2654
    my ( $record, $frameworkcode, $limit_table, $biblionumber, $filter ) = @_;
2626
2655
2627
    my $result = {};
2656
    my $result = {};
2628
    if (!defined $record) {
2657
    if (!defined $record) {
Lines 2645-2650 sub TransformMarcToKoha { Link Here
2645
        $tables{'biblioitems'} = 1;
2674
        $tables{'biblioitems'} = 1;
2646
    }
2675
    }
2647
2676
2677
    # apply permissions
2678
    if ( C4::Context->preference('MARCPermissions') && $biblionumber && $filter) {
2679
        $record = ApplyMarcPermissions({
2680
                biblionumber => $biblionumber,
2681
                record => $record,
2682
                frameworkcode => $frameworkcode,
2683
                filter => $filter,
2684
                nolog => 1
2685
            });
2686
    }
2687
2648
    # traverse through record
2688
    # traverse through record
2649
  MARCFIELD: foreach my $field ( $record->fields() ) {
2689
  MARCFIELD: foreach my $field ( $record->fields() ) {
2650
        my $tag = $field->tag();
2690
        my $tag = $field->tag();
Lines 2941-2950 sub ModZebra { Link Here
2941
    # replaced by a zebraqueue table, that is filled with ModZebra to run.
2981
    # replaced by a zebraqueue table, that is filled with ModZebra to run.
2942
    # the table is emptied by rebuild_zebra.pl script (using the -z switch)
2982
    # the table is emptied by rebuild_zebra.pl script (using the -z switch)
2943
    my $check_sql = "SELECT COUNT(*) FROM zebraqueue
2983
    my $check_sql = "SELECT COUNT(*) FROM zebraqueue
2944
    WHERE server = ?
2984
                     WHERE server = ?
2945
        AND   biblio_auth_number = ?
2985
                     AND   biblio_auth_number = ?
2946
        AND   operation = ?
2986
                     AND   operation = ?
2947
        AND   done = 0";
2987
                     AND   done = 0";
2948
    my $check_sth = $dbh->prepare_cached($check_sql);
2988
    my $check_sth = $dbh->prepare_cached($check_sql);
2949
    $check_sth->execute( $server, $biblionumber, $op );
2989
    $check_sth->execute( $server, $biblionumber, $op );
2950
    my ($count) = $check_sth->fetchrow_array;
2990
    my ($count) = $check_sth->fetchrow_array;
Lines 3440-3446 sub _koha_delete_biblioitems { Link Here
3440
3480
3441
=head2 ModBiblioMarc
3481
=head2 ModBiblioMarc
3442
3482
3443
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3483
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode,$filter);
3444
3484
3445
Add MARC data for a biblio to koha 
3485
Add MARC data for a biblio to koha 
3446
3486
Lines 3451-3457 Function exported, but should NOT be used, unless you really know what you're do Link Here
3451
sub ModBiblioMarc {
3491
sub ModBiblioMarc {
3452
    # pass the MARC::Record to this function, and it will create the records in
3492
    # pass the MARC::Record to this function, and it will create the records in
3453
    # the marc field
3493
    # the marc field
3454
    my ( $record, $biblionumber, $frameworkcode ) = @_;
3494
    my ( $record, $biblionumber, $frameworkcode, $filter ) = @_;
3495
3455
    if ( !$record ) {
3496
    if ( !$record ) {
3456
        carp 'ModBiblioMarc passed an undefined record';
3497
        carp 'ModBiblioMarc passed an undefined record';
3457
        return;
3498
        return;
Lines 3497-3502 sub ModBiblioMarc { Link Here
3497
      $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3538
      $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3498
    }
3539
    }
3499
3540
3541
    # apply permissions
3542
    if ( C4::Context->preference('MARCPermissions') ) {
3543
        $record = ApplyMarcPermissions({
3544
                biblionumber => $biblionumber,
3545
                record => $record,
3546
                frameworkcode => $frameworkcode,
3547
                filter => $filter
3548
            });
3549
    }
3550
3500
    $sth = $dbh->prepare("UPDATE biblioitems SET marc=?,marcxml=? WHERE biblionumber=?");
3551
    $sth = $dbh->prepare("UPDATE biblioitems SET marc=?,marcxml=? WHERE biblionumber=?");
3501
    $sth->execute( $record->as_usmarc(), $record->as_xml_record($encoding), $biblionumber );
3552
    $sth->execute( $record->as_usmarc(), $record->as_xml_record($encoding), $biblionumber );
3502
    $sth->finish;
3553
    $sth->finish;
Lines 3793-3798 sub RemoveAllNsb { Link Here
3793
    return $record;
3844
    return $record;
3794
}
3845
}
3795
3846
3847
=head2 ApplyMarcPermissions
3848
3849
    my $record = ApplyMarcPermissions($arguments)
3850
3851
Applies marc permission rules to a record.
3852
3853
C<$arguments> is expected to be a hashref with below keys defined.
3854
3855
=over 4
3856
3857
=item C<biblionumber>
3858
biblionumber of old record
3859
3860
=item C<record>
3861
record that will modify old record
3862
3863
=item C<frameworkcode>
3864
only tags included in framework will be processed
3865
3866
=item C<filter>
3867
hashref containing at least one filter module from the marc_permissions_modules
3868
table in form {module => filter}. Three predefined filter modules exists:
3869
3870
    * source
3871
    * category
3872
    * borrower
3873
3874
=item C<log>
3875
optional reference to array that will be filled with rule evaluation log
3876
entries.
3877
3878
=item C<nolog>
3879
optional boolean which when true disables logging to action log.
3880
3881
=back
3882
3883
Returns:
3884
3885
=over 4
3886
3887
=item C<$record>
3888
3889
new MARC record based on C<record> with C<filter> applied. If no old
3890
record for C<biblionumber> can be found, C<record> is returned unchanged.
3891
Default action when no matching filter found is to leave old record unchanged.
3892
3893
=back
3894
3895
=cut
3896
3897
sub ApplyMarcPermissions {
3898
    my $arguments     = shift;
3899
    my $biblionumber  = $arguments->{biblionumber};
3900
    my $new_record    = $arguments->{record};
3901
    my $frameworkcode = $arguments->{frameworkcode} || '';
3902
3903
    if ( !$biblionumber ) {
3904
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3905
        return;
3906
    }
3907
    if ( !$new_record ) {
3908
        carp 'ApplyMarcPermissions called on undefined record';
3909
        return;
3910
    }
3911
    my $filter = $arguments->{filter}
3912
      or return $new_record;
3913
3914
    my $log   = $arguments->{log} || [];
3915
    my $nolog = $arguments->{nolog};
3916
3917
    my $dbh = C4::Context->dbh;
3918
3919
    my $is_regex = sub {
3920
        my ( $tag, $m ) = @_;
3921
        # tag is not exactly same as possible regex
3922
        $tag ne $m &&
3923
3924
        # wildcard
3925
        $m ne '*' &&
3926
3927
        # valid tagDataType
3928
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3929
        ([1-9A-z][\dA-z]{2})$/x &&
3930
3931
        # nor valid controltagDataType
3932
        $m !~ /00[1-9A-Za-z]{1}/ &&
3933
3934
        # so we try it as a regex
3935
        $tag =~ /^$m$/
3936
    };
3937
3938
    my $old_record = GetMarcBiblio($biblionumber);
3939
    if ( $old_record ) {
3940
        my $ret_record      = MARC::Record->new();
3941
        my $perm            = GetMarcPermissions() or return $new_record;
3942
        my $marc_struct     = GetMarcStructure(1, $frameworkcode);
3943
        my $similarity_pref = C4::Context->preference("MARCPermissionsCorrectionSimilarity");
3944
        $similarity_pref    = defined $similarity_pref &&
3945
                              $similarity_pref >= 0 &&
3946
                              $similarity_pref <= 100 ?
3947
                              $similarity_pref/100.0 : undef;
3948
3949
        # First came the leader ...
3950
        $ret_record->leader($old_record->leader());
3951
3952
        # ... and then came all the tags in the framework
3953
        for my $tag (sort keys %{$marc_struct}) {
3954
            my @old_fields = $old_record->field($tag);
3955
            my @new_fields = $new_record->field($tag);
3956
            next unless @old_fields or @new_fields;
3957
3958
            my @perms = (
3959
                $perm->{'*'},
3960
                (   # regex tags
3961
                    map { $perm->{$_} }
3962
                      grep { $is_regex->( $tag, $_ ) } sort keys %{$perm}
3963
                ),
3964
                $perm->{$tag}
3965
            );
3966
3967
            # There can be more than one field for each tag, so we have to
3968
            # iterate all fields and distinguish which are new, removed or
3969
            # modified
3970
            my $max_fields = ($#old_fields, $#new_fields)
3971
                             [$#old_fields< $#new_fields];
3972
            for ( my $i_field = 0; $i_field <= $max_fields; $i_field++ ) {
3973
                my $old_field = $old_fields[$i_field];
3974
                my $new_field = $new_fields[$i_field];
3975
3976
                # Existing field
3977
                if ( $old_field and $new_field ) {
3978
                    # Control fields
3979
                    if ( $old_field->is_control_field() ) {
3980
                        # Existing control field
3981
                        if ( $old_field and $new_field ) {
3982
                            my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @perms);
3983
                            if ( defined $on_existing->{action} ) {
3984
                                push @{$log},
3985
                                  {
3986
                                    rule         => $on_existing->{rule},
3987
                                    event        => "existing",
3988
                                    action       => $on_existing->{action},
3989
                                    tag          => $tag,
3990
                                    subfieldcode => undef,
3991
                                    filter       => $filter
3992
                                  };
3993
                                if ( $on_existing->{action} eq 'skip' ) {
3994
                                    $ret_record->insert_fields_ordered($old_field);
3995
                                    next;
3996
                                } elsif ( $on_existing->{action} eq 'overwrite' ) {
3997
                                    $ret_record->insert_fields_ordered($new_field);
3998
                                    next;
3999
                                } elsif ( $on_existing->{action} eq 'add' ) {
4000
                                    $ret_record->insert_fields_ordered($old_field);
4001
                                    $ret_record->insert_fields_ordered($new_field);
4002
                                    next;
4003
                                } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
4004
                                    if ( $similarity_pref ) {
4005
                                        my $similarity = similarity $old_field->data(), $new_field->data();
4006
                                        if ( $similarity >= $similarity_pref ) {
4007
                                            $ret_record->insert_fields_ordered($new_field);
4008
                                        } else {
4009
                                            $ret_record->insert_fields_ordered($old_field);
4010
                                            $ret_record->insert_fields_ordered($new_field);
4011
                                        }
4012
                                    } else {
4013
                                        $ret_record->insert_fields_ordered($old_field);
4014
                                    }
4015
                                }
4016
                            } else { # default to skip
4017
                                $ret_record->insert_fields_ordered($old_field);
4018
                                next;
4019
                            }
4020
                        # New control field
4021
                        } elsif ( not $old_field and $new_field ) {
4022
                            my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
4023
                            if ( defined $on_new->{action} ) {
4024
                                push @{$log},
4025
                                  {
4026
                                    rule         => $on_new->{rule},
4027
                                    event        => "new",
4028
                                    action       => $on_new->{action},
4029
                                    tag          => $tag,
4030
                                    subfieldcode => undef,
4031
                                    filter       => $filter
4032
                                  };
4033
                                if ( $on_new->{action} eq 'skip' ) {
4034
                                    next;
4035
                                } elsif ( $on_new->{action} eq 'add' ) {
4036
                                    $ret_record->insert_fields_ordered($new_field);
4037
                                    next;
4038
                                }
4039
                            }
4040
                        # Removed control field
4041
                        } elsif ( $old_field and not $new_field ) {
4042
                            my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4043
                            if ( defined $on_removed->{action} ) {
4044
                                push @{$log},
4045
                                  {
4046
                                    rule         => $on_removed->{rule},
4047
                                    event        => "removed",
4048
                                    action       => $on_removed->{action},
4049
                                    tag          => $tag,
4050
                                    subfieldcode => undef
4051
                                  };
4052
                                if ( $on_removed->{action} eq 'skip' ) {
4053
                                    $ret_record->insert_fields_ordered($old_field);
4054
                                    next;
4055
                                } elsif ( $on_removed->{action} eq 'remove' ) {
4056
                                    next;
4057
                                }
4058
                            } else { # default to skip
4059
                                $ret_record->insert_fields_ordered($old_field);
4060
                                next;
4061
                            }
4062
                        }
4063
                    # Indicators and subfields
4064
                    } else {
4065
                        # Indicators
4066
                        my @old_ind = map { $old_field->indicator($_) } (1,2);
4067
                        my @new_ind = map { $new_field->indicator($_) } (1,2);
4068
                        my @ind = ('','');
4069
4070
                        for my $i ((0,1)) {
4071
                            my @ind_perms = (
4072
                                @perms,
4073
                                $perm->{$tag}->{subfields}->{ 'i' . ( $i + 1 ) }
4074
                            );
4075
4076
                            # Existing indicator
4077
                            if ( defined $old_ind[$i] and defined $new_ind[$i] ) {
4078
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @ind_perms);
4079
                                if ( defined $on_existing->{action} ) {
4080
                                    push @{$log},
4081
                                      {
4082
                                        rule         => $on_existing->{rule},
4083
                                        event        => "existing",
4084
                                        action       => $on_existing->{action},
4085
                                        tag          => $tag,
4086
                                        subfieldcode => 'i' . ( $i + 1 ),
4087
                                        filter       => $filter
4088
                                      };
4089
                                    if ( $on_existing->{action} eq 'skip' ) {
4090
                                        $ind[$i] = $old_ind[$i];
4091
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4092
                                        $ind[$i] = $new_ind[$i];
4093
                                    }
4094
                                } else { # default to skip
4095
                                    $ind[$i] = $old_ind[$i];
4096
                                }
4097
                            # New indicator
4098
                            } elsif ( not defined $old_ind[$i] and defined $new_ind[$i] ) {
4099
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @ind_perms);
4100
                                if ( defined $on_new->{action} ) {
4101
                                    push @{$log},
4102
                                      {
4103
                                        rule         => $on_new->{rule},
4104
                                        event        => "new",
4105
                                        action       => $on_new->{action},
4106
                                        tag          => $tag,
4107
                                        subfieldcode => 'i' . ( $i + 1 ),
4108
                                        filter       => $filter
4109
                                      };
4110
                                    if ( $on_new->{action} eq 'skip' ) {
4111
                                        $ind[$i] = $old_ind[$i];
4112
                                    } elsif ( $on_new->{action} eq 'add' ) {
4113
                                        $ind[$i] = $new_ind[$i];
4114
                                    }
4115
                                }
4116
                            # Removed indicator
4117
                            } elsif ( defined $old_ind[$i] and not defined $new_ind[$i] ) {
4118
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @ind_perms);
4119
                                if ( defined $on_removed->{action} ) {
4120
                                    push @{$log},
4121
                                      {
4122
                                        rule         => $on_removed->{rule},
4123
                                        event        => "removed",
4124
                                        action       => $on_removed->{action},
4125
                                        tag          => $tag,
4126
                                        subfieldcode => 'i' . ( $i + 1 ),
4127
                                        filter       => $filter
4128
                                      };
4129
                                    if ( $on_removed->{action} eq 'skip' ) {
4130
                                        $ind[$i] = $old_ind[$i];
4131
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4132
                                        $ind[$i] = '';
4133
                                    }
4134
                                } else { # default to skip
4135
                                    $ind[$i] = $old_ind[$i];
4136
                                }
4137
                            }
4138
                        }
4139
4140
                        # Subfields
4141
                        my @subfields = ();
4142
                        my %proccessed_subcodes = ();
4143
4144
                        # Try to preserve subfield order by first processing
4145
                        # subfields from the old record, and then subfields from
4146
                        # the framework structure unless include in old record.
4147
                        my @sorted_subfields = (
4148
                            map { shift @{$_} } $old_field->subfields(),
4149
                            () = sort keys %{ $marc_struct->{$tag} }
4150
                        );
4151
                        for my $subcode ( @sorted_subfields ) {
4152
                            next if $proccessed_subcodes{$subcode};
4153
                            $proccessed_subcodes{$subcode} = 1;
4154
                            next unless ref $marc_struct->{$tag}{$subcode} eq 'HASH';
4155
                            my @old_subfields = $old_field->subfield($subcode);
4156
                            my @new_subfields = $new_field->subfield($subcode);
4157
                            next unless @old_subfields or @new_subfields;
4158
4159
                            my @subfield_perms = (
4160
                                @perms,
4161
                                $perm->{'*'}->{subfields}->{'*'},
4162
                                $perm->{$tag}->{subfields}->{'*'},
4163
                                (    # tag->regex
4164
                                    map { $perm->{$tag}->{subfields}->{$_} }
4165
                                      grep {
4166
                                        # subcode is not exactly same as
4167
                                        # possible regex
4168
                                        $subcode ne $_ &&
4169
4170
                                        # wildcard, not checking for valid
4171
                                        # subfieldcodeDataType since it
4172
                                        # contains too many regex characters
4173
                                        $_ !~ /^(\*)$/ &&
4174
4175
                                        # so we try it as a regex
4176
                                        $subcode =~ /$_/
4177
                                      } sort
4178
                                      keys %{ $perm->{$tag}->{subfields} }
4179
                                ),
4180
                                (    # regex->*
4181
                                    map { $perm->{$_}->{subfields}->{'*'} }
4182
                                      grep { $is_regex->( $tag, $_ ) }
4183
                                      sort keys %{$perm}
4184
                                ),
4185
                                $perm->{'*'}->{subfields}->{$subcode},
4186
                                (    # regex->subcode
4187
                                    map { $perm->{$_}->{subfields}->{$subcode} }
4188
                                      grep { $is_regex->( $tag, $_ ) }
4189
                                      sort keys %{$perm}
4190
                                ),
4191
                                $perm->{$tag}->{subfields}->{$subcode}
4192
                            );
4193
4194
                            # Existing subfield
4195
                            if ( @old_subfields and @new_subfields ) {
4196
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @subfield_perms);
4197
                                if ( defined $on_existing->{action} ) {
4198
                                    push @{$log},
4199
                                      {
4200
                                        rule         => $on_existing->{rule},
4201
                                        event        => "existing",
4202
                                        action       => $on_existing->{action},
4203
                                        tag          => $tag,
4204
                                        subfieldcode => $subcode,
4205
                                        filter       => $filter
4206
                                      };
4207
                                    if ( $on_existing->{action} eq 'skip' ) {
4208
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4209
                                        next;
4210
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4211
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4212
                                        next;
4213
                                    } elsif ( $on_existing->{action} eq 'add' ) {
4214
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4215
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4216
                                        next;
4217
                                    } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
4218
                                        if ( $similarity_pref ) {
4219
                                            my @corrected_vals = ();
4220
4221
                                            # correct all old subfields
4222
                                            for ( my $i_new = 0; $i_new <= $#new_subfields; $i_new++ ) {
4223
                                                my $new_val = $new_subfields[$i_new];
4224
                                                for ( my $i_old = 0; $i_old <= $#old_subfields; $i_old++ ) {
4225
                                                    my $old_val = $old_subfields[$i_old];
4226
                                                    next if not defined $old_val or not defined $new_val;
4227
                                                    my $similarity = similarity $old_val, $new_val;
4228
                                                    if ( $similarity >= $similarity_pref ) {
4229
                                                        $corrected_vals[$i_old] = $new_val;
4230
                                                        $new_subfields[$i_new] = undef;
4231
                                                        $old_subfields[$i_old] = undef;
4232
                                                        last;
4233
                                                    }
4234
                                                }
4235
                                            }
4236
4237
                                            # insert all unchanged old subfields
4238
                                            map {
4239
                                                $corrected_vals[$_] = $old_subfields[$_]
4240
                                                    if defined $old_subfields[$_]
4241
                                                } 0 .. $#old_subfields;
4242
4243
4244
                                            # append all remaning new subfields
4245
                                            map {push @corrected_vals, $_ if defined} @new_subfields;
4246
4247
                                            push(@subfields, map {($subcode,$_)} @corrected_vals);
4248
                                        } else {
4249
                                            push(@subfields, map {($subcode,$_)} @old_subfields);
4250
                                        }
4251
                                    }
4252
                                } else { # default to skip
4253
                                    push(@subfields, map {($subcode,$_)} @old_subfields);
4254
                                    next;
4255
                                }
4256
                            # New subfield
4257
                            } elsif ( not @old_subfields and @new_subfields ) {
4258
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @subfield_perms);
4259
                                if ( defined $on_new->{action} ) {
4260
                                    push @{$log},
4261
                                      {
4262
                                        rule         => $on_new->{rule},
4263
                                        event        => "new",
4264
                                        action       => $on_new->{action},
4265
                                        tag          => $tag,
4266
                                        subfieldcode => $subcode,
4267
                                        filter       => $filter
4268
                                      };
4269
                                    if ( $on_new->{action} eq 'skip' ) {
4270
                                        next;
4271
                                    } elsif ( $on_new->{action} eq 'add' ) {
4272
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4273
                                        next;
4274
                                    }
4275
                                }
4276
                            # Removed subfield
4277
                            } elsif ( @old_subfields and not @new_subfields ) {
4278
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @subfield_perms);
4279
                                if ( defined $on_removed->{action} ) {
4280
                                    push @{$log},
4281
                                      {
4282
                                        rule         => $on_removed->{rule},
4283
                                        event        => "removed",
4284
                                        action       => $on_removed->{action},
4285
                                        tag          => $tag,
4286
                                        subfieldcode => $subcode,
4287
                                        filter       => $filter
4288
                                      };
4289
                                    if ( $on_removed->{action} eq 'skip' ) {
4290
                                        push(@subfields, ($subcode => @old_subfields));
4291
                                        next;
4292
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4293
                                        next;
4294
                                    }
4295
                                } else { # default to skip
4296
                                    push(@subfields, ($subcode => @old_subfields));
4297
                                    next;
4298
                                }
4299
                            }
4300
                        } # / for each subfield
4301
                        $ret_record->insert_grouped_field(MARC::Field->new($tag, $ind[0], $ind[1], @subfields));
4302
                    }
4303
                # New field
4304
                } elsif ( not $old_field and $new_field ) {
4305
                    my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
4306
                    if ( defined $on_new->{action} ) {
4307
                        push @{$log},
4308
                          {
4309
                            rule         => $on_new->{rule},
4310
                            event        => "new",
4311
                            action       => $on_new->{action},
4312
                            tag          => $tag,
4313
                            subfieldcode => undef,
4314
                            filter       => $filter
4315
                          };
4316
                        if ( $on_new->{action} eq 'skip' ) {
4317
                            next;
4318
                        } elsif ( $on_new->{action} eq 'add' ) {
4319
                            $ret_record->insert_fields_ordered($new_field);
4320
                            next;
4321
                        }
4322
                    }
4323
                # Removed field
4324
                } elsif ( $old_field and not $new_field ) {
4325
                    my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4326
                    if ( defined $on_removed->{action} ) {
4327
                        push @{$log},
4328
                          {
4329
                            rule         => $on_removed->{rule},
4330
                            event        => "removed",
4331
                            action       => $on_removed->{action},
4332
                            tag          => $tag,
4333
                            subfieldcode => undef,
4334
                            filter       => $filter
4335
                          };
4336
                        if ( $on_removed->{action} eq 'skip' ) {
4337
                            $ret_record->insert_grouped_field($old_field);
4338
                            next;
4339
                        } elsif ( $on_removed->{action} eq 'remove' ) {
4340
                            next;
4341
                        }
4342
                    } else { # default to skip
4343
                        $ret_record->insert_grouped_field($old_field);
4344
                        next;
4345
                    }
4346
                }
4347
            } # / for each field
4348
        } # / for each tag
4349
        if ( !$nolog && C4::Context->preference("MARCPermissionsLog") ) {
4350
            my $log_str = '';
4351
            my $n       = $#{$log};
4352
            for my $l ( @{$log} ) {
4353
                $log_str .= "{\n";
4354
                my $kn = keys %{$l};
4355
                for my $k ( sort keys %{$l} ) {
4356
                    if ( $k eq 'filter' ) {
4357
                        $log_str .= "  filter => {\n";
4358
                        my $fkn = keys %{ $l->{$k} };
4359
                        for my $fk ( sort keys %{ $l->{$k} } ) {
4360
                            $log_str .= '    '
4361
                              . $fk . ' => '
4362
                              . $l->{$k}->{$fk}
4363
                              . ( --$fkn ? ',' : '' ) . "\n";
4364
                        }
4365
                        $log_str .= "  }" . ( --$kn ? ',' : '' ) . "\n";
4366
                    }
4367
                    else {
4368
                        $log_str .= '  '
4369
                          . $k . ' => '
4370
                          . $l->{$k}
4371
                          . ( --$kn ? ',' : '' ) . "\n"
4372
                          if defined $l->{$k};
4373
                    }
4374
                }
4375
                $log_str .= '}' . ( $n-- ? ",\n" : '' );
4376
            }
4377
            logaction( "CATALOGUING", "MODIFY", $biblionumber, $log_str );
4378
        }
4379
        return $ret_record;
4380
    }
4381
    return $new_record;
4382
}
4383
4384
4385
=head2 GetMarcPermissions
4386
4387
    my $marc_permissions = GetMarcPermissions()
4388
4389
Loads MARC field permissions from the marc_permissions table.
4390
4391
Returns:
4392
4393
=over 4
4394
4395
=item C<$marc_permissions>
4396
4397
hashref with permissions structure for use with GetMarcPermissionsAction.
4398
4399
=back
4400
4401
=cut
4402
4403
sub GetMarcPermissions {
4404
    my $dbh = C4::Context->dbh;
4405
    my $rule_count = 0;
4406
    my %perms = ();
4407
4408
    my $query = '
4409
    SELECT `marc_permissions`.*,
4410
           `marc_permissions_modules`.`name`,
4411
           `marc_permissions_modules`.`description`,
4412
           `marc_permissions_modules`.`specificity`
4413
    FROM `marc_permissions`
4414
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4415
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4416
    ';
4417
    my $sth = $dbh->prepare($query);
4418
    $sth->execute();
4419
    while ( my $row = $sth->fetchrow_hashref ) {
4420
        $rule_count++;
4421
        if ( defined $row->{tagsubfield} and $row->{tagsubfield} ) {
4422
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4423
              ->{on_existing}->{ $row->{name} }->{ $row->{filter} } =
4424
              { action => $row->{on_existing}, rule => $row->{'id'} };
4425
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4426
              ->{on_new}->{ $row->{name} }->{ $row->{filter} } =
4427
              { action => $row->{on_new}, rule => $row->{'id'} };
4428
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4429
              ->{on_removed}->{ $row->{name} }->{ $row->{filter} } =
4430
              { action => $row->{on_removed}, rule => $row->{'id'} };
4431
        }
4432
        else {
4433
            $perms{ $row->{tagfield} }->{on_existing}->{ $row->{name} }
4434
              ->{ $row->{filter} } =
4435
              { action => $row->{on_existing}, rule => $row->{'id'} };
4436
            $perms{ $row->{tagfield} }->{on_new}->{ $row->{name} }
4437
              ->{ $row->{filter} } =
4438
              { action => $row->{on_new}, rule => $row->{'id'} };
4439
            $perms{ $row->{tagfield} }->{on_removed}->{ $row->{name} }
4440
              ->{ $row->{filter} } =
4441
              { action => $row->{on_removed}, rule => $row->{'id'} };
4442
        }
4443
    }
4444
4445
    return unless $rule_count;
4446
    return \%perms;
4447
}
4448
4449
=head2 GetMarcPermissionsAction
4450
4451
    my $action = GetMarcPermissionsAction($event, $filter, @permissions)
4452
4453
Gets action based on C<$event>, C<$filter> and C<@permissions>.
4454
4455
=over 4
4456
4457
=item C<$event>
4458
4459
which event: 'on_existing', 'on_new' or 'on_removed'
4460
4461
=item C<$filter>
4462
4463
hashref containing at least one filter module from the marc_permissions_modules
4464
table in form {module => filter}. Three predefined filter modules exists:
4465
4466
    * source
4467
    * category
4468
    * borrower
4469
4470
=item C<@permissions>
4471
4472
list of permission structures in order of specificity from least to most
4473
specific.
4474
4475
=back
4476
4477
Returns:
4478
4479
=over 4
4480
4481
=item C<$action>
4482
4483
hashref defining matching action.
4484
4485
=back
4486
4487
=cut
4488
4489
sub GetMarcPermissionsAction {
4490
    my $what = shift or return;
4491
    my $filter = shift or return;
4492
    my @perms = @_;
4493
4494
    my $modules = GetMarcPermissionsModules();
4495
4496
    my $action = undef;
4497
    for my $perm (@perms) {
4498
        next if not defined $perm;
4499
        next if not defined $perm->{$what};
4500
        my $tmp_action = undef;
4501
4502
        for my $module ( @{$modules} ) {
4503
            if (defined $perm->{$what}->{ $module->{'name'} }
4504
                and
4505
                defined $perm->{$what}->{ $module->{'name'} }->{'*'}->{action} )
4506
            {
4507
                $tmp_action = $perm->{$what}->{ $module->{'name'} }->{'*'};
4508
            }
4509
            if (    defined $filter->{ $module->{'name'} }
4510
                and defined $perm->{$what}->{ $module->{'name'} }
4511
                and defined $perm->{$what}->{ $module->{'name'} }
4512
                ->{ $filter->{ $module->{'name'} } }->{action} )
4513
            {
4514
                $tmp_action = $perm->{$what}->{ $module->{'name'} }
4515
                  ->{ $filter->{ $module->{'name'} } };
4516
            }
4517
        }
4518
4519
        $action = $tmp_action if defined $tmp_action;
4520
    }
4521
4522
    return $action;
4523
}
4524
4525
=head2 GetMarcPermissionsRules
4526
4527
    my $rules = GetMarcPermissionsRules()
4528
4529
Returns:
4530
4531
=over 4
4532
4533
=item C<$rules>
4534
4535
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
4536
table in order of module specificity and rule id.
4537
4538
=back
4539
4540
=cut
4541
4542
sub GetMarcPermissionsRules {
4543
    my $dbh = C4::Context->dbh;
4544
    my @rules = ();
4545
4546
    my $query = '
4547
    SELECT `marc_permissions`.`id`,
4548
           `marc_permissions`.`tagfield`,
4549
           `marc_permissions`.`tagsubfield`,
4550
           `marc_permissions`.`filter`,
4551
           `marc_permissions`.`on_existing`,
4552
           `marc_permissions`.`on_new`,
4553
           `marc_permissions`.`on_removed`,
4554
           `marc_permissions_modules`.`name` as  `module`,
4555
           `marc_permissions_modules`.`description`,
4556
           `marc_permissions_modules`.`specificity`
4557
    FROM `marc_permissions`
4558
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4559
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4560
    ';
4561
    my $sth = $dbh->prepare($query);
4562
    $sth->execute();
4563
    while ( my $row = $sth->fetchrow_hashref ) {
4564
        push(@rules, $row);
4565
    }
4566
4567
    return wantarray ? @rules : \@rules;
4568
}
4569
4570
=head2 GetMarcPermissionsModules
4571
4572
    my $modules = GetMarcPermissionsModules()
4573
4574
Returns:
4575
4576
=over 4
4577
4578
=item C<$modules>
4579
4580
array (in list context, arrayref otherwise) of hashrefs from
4581
marc_permissions_modules table in order of specificity.
4582
4583
=back
4584
4585
=cut
4586
4587
sub GetMarcPermissionsModules {
4588
    my $dbh = C4::Context->dbh;
4589
    my @modules = ();
4590
4591
    my $query = '
4592
    SELECT *
4593
    FROM `marc_permissions_modules`
4594
    ORDER BY `specificity`
4595
    ';
4596
    my $sth = $dbh->prepare($query);
4597
    $sth->execute();
4598
    while ( my $row = $sth->fetchrow_hashref ) {
4599
        push(@modules, $row);
4600
    }
4601
4602
    return wantarray ? @modules : \@modules;
4603
}
4604
4605
=head2 ModMarcPermissionsRule
4606
4607
    my $success = ModMarcPermissionsRule($id, $fields)
4608
4609
Modifies rule in the marc_permissions table.
4610
4611
=over 4
4612
4613
=item C<$id>
4614
4615
rule id to modify
4616
4617
=item C<$fields>
4618
4619
hashref defining the table fields
4620
4621
      * tagfield - required
4622
      * tagsubfield
4623
      * module - required
4624
      * filter - required
4625
      * on_existing - required
4626
      * on_new - required
4627
      * on_removed - required
4628
4629
=back
4630
4631
Returns:
4632
4633
=over 4
4634
4635
=item C<$success>
4636
4637
undef if an error occurs, otherwise true.
4638
4639
=back
4640
4641
=cut
4642
4643
sub ModMarcPermissionsRule {
4644
    my ($id, $f) = @_;
4645
    my $dbh = C4::Context->dbh;
4646
    my $query = '
4647
    UPDATE `marc_permissions`
4648
    SET
4649
      tagfield = ?,
4650
      tagsubfield = ?,
4651
      module = ?,
4652
      filter = ?,
4653
      on_existing = ?,
4654
      on_new = ?,
4655
      on_removed = ?
4656
    WHERE
4657
      id = ?
4658
    ';
4659
    my $sth = $dbh->prepare($query);
4660
    return $sth->execute (
4661
                      $f->{tagfield},
4662
                      $f->{tagsubfield},
4663
                      $f->{module},
4664
                      $f->{filter},
4665
                      $f->{on_existing},
4666
                      $f->{on_new},
4667
                      $f->{on_removed},
4668
                      $id
4669
                  );
4670
}
4671
4672
=head2 AddMarcPermissionsRule
4673
4674
    my $success = AddMarcPermissionsRule($fields)
4675
4676
Add rule to the marc_permissions table.
4677
4678
=over 4
4679
4680
=item C<$fields>
4681
4682
hashref defining the table fields
4683
4684
      tagfield - required
4685
      tagsubfield
4686
      module - required
4687
      filter - required
4688
      on_existing - required
4689
      on_new - required
4690
      on_removed - required
4691
4692
=back
4693
4694
Returns:
4695
4696
=over 4
4697
4698
=item C<$success>
4699
4700
undef if an error occurs, otherwise true.
4701
4702
=back
4703
4704
=cut
4705
4706
sub AddMarcPermissionsRule {
4707
    my $f = shift;
4708
    my $dbh = C4::Context->dbh;
4709
    my $query = '
4710
    INSERT INTO `marc_permissions`
4711
    (
4712
      tagfield,
4713
      tagsubfield,
4714
      module,
4715
      filter,
4716
      on_existing,
4717
      on_new,
4718
      on_removed
4719
    )
4720
    VALUES (?, ?, ?, ?, ?, ?, ?)
4721
    ';
4722
    my $sth = $dbh->prepare($query);
4723
    return $sth->execute (
4724
                      $f->{tagfield},
4725
                      $f->{tagsubfield},
4726
                      $f->{module},
4727
                      $f->{filter},
4728
                      $f->{on_existing},
4729
                      $f->{on_new},
4730
                      $f->{on_removed}
4731
                  );
4732
}
4733
4734
=head2 DelMarcPermissionsRule
4735
4736
    my $success = DelMarcPermissionsRule($id)
4737
4738
Deletes rule from the marc_permissions table.
4739
4740
=over 4
4741
4742
=item C<$id>
4743
4744
rule id to delete
4745
4746
=back
4747
4748
Returns:
4749
4750
=over 4
4751
4752
=item C<$success>
4753
4754
undef if an error occurs, otherwise true.
4755
4756
=back
4757
4758
=cut
4759
4760
sub DelMarcPermissionsRule {
4761
    my $id = shift;
4762
    my $dbh = C4::Context->dbh;
4763
    my $query = '
4764
    DELETE FROM `marc_permissions`
4765
    WHERE
4766
      id = ?
4767
    ';
4768
    my $sth = $dbh->prepare($query);
4769
    return $sth->execute($id);
4770
}
4771
3796
1;
4772
1;
3797
4773
3798
4774
(-)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 / +4 lines)
Lines 83-90 Link Here
83
                    <dd>Manage rules for automatically matching MARC records during record imports.</dd>
83
                    <dd>Manage rules for automatically matching MARC records during record imports.</dd>
84
                    <dt><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></dt>
84
                    <dt><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></dt>
85
                    <dd>Manage OAI Sets</dd>
85
                    <dd>Manage OAI Sets</dd>
86
                    <dt><a href="/cgi-bin/koha/admin/items_search_fields.pl">Item search fields</a></dt>
86
                    <dt><a href="/cgi-bin/koha/admin/items_search_fields.pl">Items search fields</a></dt>
87
                    <dd>Manage custom fields for item search.</dd>
87
                    <dd>Manage custom fields for items search.</dd>
88
                    <dt><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></dt>
89
                    <dd>Managed MARC field permissions</dd>
88
                </dl>
90
                </dl>
89
91
90
                <h3>Acquisition parameters</h3>
92
                <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 224-249 Cataloging: Link Here
224
                  yes: "do"
224
                  yes: "do"
225
                  no: "don't"
225
                  no: "don't"
226
            - 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.
226
            - 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.
227
    Exporting:
228
        -
227
        -
229
            - Include following fields when exporting BibTeX,
228
            - When importing records
230
            - pref: BibtexExportAdditionalFields
229
            - pref: MARCPermissions
231
              type: textarea
230
              choices:
232
            - "Use one line per tag in the format BT_TAG: TAG$SUBFIELD ( e.g. lccn: 010$a )"
231
                  yes: "use"
233
            - "<br/>"
232
                  no: "don't use"
234
            - "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] )"
233
            - MARC permissions rules to decide which action to take for each field.
235
            - "<br/>"
234
            - When <b>add_or_correct</b>, consider a field modification to be a correction if the old and new value has a similarity of
236
            - "All values of repeating tags and subfields will be printed with the given BibTeX tag."
235
            - pref: MARCPermissionsCorrectionSimilarity
237
            - "<br/>"
236
            - %. If similarity falls below this value, the new value will be treated as a new field.
238
            - "Use '@' ( with quotes ) as the BT_TAG to replace the bibtex record type with a field value of your choosing."
239
        -
240
            - Include following fields when exporting RIS,
241
            - pref: RisExportAdditionalFields
242
              type: textarea
243
            - "Use one line per tag in the format RIS_TAG: TAG$SUBFIELD ( e.g. LC: 010$a )"
244
            - "<br/>"
245
            - "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] )"
246
            - "<br/>"
247
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
248
            - "<br/>"
249
            - "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