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

(-)a/C4/Biblio.pm (-9 / +959 lines)
Lines 106-111 BEGIN { Link Here
106
      &CountItemsIssued
106
      &CountItemsIssued
107
      &CountBiblioInOrders
107
      &CountBiblioInOrders
108
      &GetSubscriptionsId
108
      &GetSubscriptionsId
109
110
      &GetMarcPermissionsRules
111
      &GetMarcPermissionsModules
112
      &ModMarcPermissionsRule
113
      &AddMarcPermissionsRule
114
      &DelMarcPermissionsRule
109
    );
115
    );
110
116
111
    # 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,
139
    # they are useful in a few circumstances, so they are exported,
134
    # but don't use them unless you are a core developer ;-)
140
    # but don't use them unless you are a core developer ;-)
135
    push @EXPORT, qw(
141
    push @EXPORT, qw(
142
      &ApplyMarcPermissions
136
      &ModBiblioMarc
143
      &ModBiblioMarc
137
    );
144
    );
138
145
Lines 257-263 sub AddBiblio { Link Here
257
    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
264
    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
258
265
259
    # now add the record
266
    # now add the record
260
    ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
267
    ModBiblioMarc( $record, $biblionumber, $frameworkcode, $options->{filter} ) unless $defer_marc_save;
261
268
262
    # update OAI-PMH sets
269
    # update OAI-PMH sets
263
    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
270
    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
Lines 270-276 sub AddBiblio { Link Here
270
277
271
=head2 ModBiblio
278
=head2 ModBiblio
272
279
273
  ModBiblio( $record,$biblionumber,$frameworkcode);
280
  ModBiblio($record, $biblionumber, $frameworkcode, $filter);
274
281
275
Replace an existing bib record identified by C<$biblionumber>
282
Replace an existing bib record identified by C<$biblionumber>
276
with one supplied by the MARC::Record object C<$record>.  The embedded
283
with one supplied by the MARC::Record object C<$record>.  The embedded
Lines 291-297 Returns 1 on success 0 on failure Link Here
291
=cut
298
=cut
292
299
293
sub ModBiblio {
300
sub ModBiblio {
294
    my ( $record, $biblionumber, $frameworkcode ) = @_;
301
    my ( $record, $biblionumber, $frameworkcode, $filter ) = @_;
302
295
    if (!$record) {
303
    if (!$record) {
296
        carp 'No record passed to ModBiblio';
304
        carp 'No record passed to ModBiblio';
297
        return 0;
305
        return 0;
Lines 329-341 sub ModBiblio { Link Here
329
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
337
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
330
338
331
    # load the koha-table data object
339
    # load the koha-table data object
332
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
340
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode, undef, $biblionumber, $filter );
333
341
334
    # update MARC subfield that stores biblioitems.cn_sort
342
    # update MARC subfield that stores biblioitems.cn_sort
335
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
343
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
336
344
337
    # update the MARC record (that now contains biblio and items) with the new record data
345
    # update the MARC record (that now contains biblio and items) with the new record data
338
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
346
    ModBiblioMarc( $record, $biblionumber, $frameworkcode, $filter );
339
347
340
    # modify the other koha tables
348
    # modify the other koha tables
341
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
349
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 2655-2661 sub TransformHtmlToMarc { Link Here
2655
2663
2656
=head2 TransformMarcToKoha
2664
=head2 TransformMarcToKoha
2657
2665
2658
  $result = TransformMarcToKoha( $record, $frameworkcode )
2666
  $result = TransformMarcToKoha( $record, $frameworkcode, $biblionumber, $filter )
2659
2667
2660
Extract data from a MARC bib record into a hashref representing
2668
Extract data from a MARC bib record into a hashref representing
2661
Koha biblio, biblioitems, and items fields. 
2669
Koha biblio, biblioitems, and items fields. 
Lines 2666-2672 hash_ref Link Here
2666
=cut
2674
=cut
2667
2675
2668
sub TransformMarcToKoha {
2676
sub TransformMarcToKoha {
2669
    my ( $record, $frameworkcode, $limit_table ) = @_;
2677
    my ( $record, $frameworkcode, $limit_table, $biblionumber, $filter ) = @_;
2670
2678
2671
    my $result = {};
2679
    my $result = {};
2672
    if (!defined $record) {
2680
    if (!defined $record) {
Lines 2687-2692 sub TransformMarcToKoha { Link Here
2687
        $tables{'biblioitems'} = 1;
2695
        $tables{'biblioitems'} = 1;
2688
    }
2696
    }
2689
2697
2698
    # apply permissions
2699
    if ( C4::Context->preference('MARCPermissions') && $biblionumber && $filter) {
2700
        $record = ApplyMarcPermissions({
2701
                biblionumber => $biblionumber,
2702
                record => $record,
2703
                frameworkcode => $frameworkcode,
2704
                filter => $filter,
2705
                nolog => 1
2706
            });
2707
    }
2708
2690
    # traverse through record
2709
    # traverse through record
2691
  MARCFIELD: foreach my $field ( $record->fields() ) {
2710
  MARCFIELD: foreach my $field ( $record->fields() ) {
2692
        my $tag = $field->tag();
2711
        my $tag = $field->tag();
Lines 3441-3447 sub _koha_delete_biblioitems { Link Here
3441
3460
3442
=head2 ModBiblioMarc
3461
=head2 ModBiblioMarc
3443
3462
3444
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3463
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode,$filter);
3445
3464
3446
Add MARC XML data for a biblio to koha
3465
Add MARC XML data for a biblio to koha
3447
3466
Lines 3452-3458 Function exported, but should NOT be used, unless you really know what you're do Link Here
3452
sub ModBiblioMarc {
3471
sub ModBiblioMarc {
3453
    # pass the MARC::Record to this function, and it will create the records in
3472
    # pass the MARC::Record to this function, and it will create the records in
3454
    # the marcxml field
3473
    # the marcxml field
3455
    my ( $record, $biblionumber, $frameworkcode ) = @_;
3474
    my ( $record, $biblionumber, $frameworkcode, $filter ) = @_;
3475
3456
    if ( !$record ) {
3476
    if ( !$record ) {
3457
        carp 'ModBiblioMarc passed an undefined record';
3477
        carp 'ModBiblioMarc passed an undefined record';
3458
        return;
3478
        return;
Lines 3465-3470 sub ModBiblioMarc { Link Here
3465
    if ( !$frameworkcode ) {
3485
    if ( !$frameworkcode ) {
3466
        $frameworkcode = "";
3486
        $frameworkcode = "";
3467
    }
3487
    }
3488
3489
    # apply permissions
3490
    if ( C4::Context->preference('MARCPermissions') ) {
3491
        $record = ApplyMarcPermissions({
3492
                biblionumber => $biblionumber,
3493
                record => $record,
3494
                frameworkcode => $frameworkcode,
3495
                filter => $filter
3496
            });
3497
    }
3498
3468
    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3499
    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3469
    $sth->execute( $frameworkcode, $biblionumber );
3500
    $sth->execute( $frameworkcode, $biblionumber );
3470
    $sth->finish;
3501
    $sth->finish;
Lines 3785-3790 sub RemoveAllNsb { Link Here
3785
    return $record;
3816
    return $record;
3786
}
3817
}
3787
3818
3819
=head2 ApplyMarcPermissions
3820
3821
    my $record = ApplyMarcPermissions($arguments)
3822
3823
Applies marc permission rules to a record.
3824
3825
C<$arguments> is expected to be a hashref with below keys defined.
3826
3827
=over 4
3828
3829
=item C<biblionumber>
3830
biblionumber of old record
3831
3832
=item C<record>
3833
record that will modify old record
3834
3835
=item C<frameworkcode>
3836
only tags included in framework will be processed
3837
3838
=item C<filter>
3839
hashref containing at least one filter module from the marc_permissions_modules
3840
table in form {module => filter}. Three predefined filter modules exists:
3841
3842
    * source
3843
    * category
3844
    * borrower
3845
3846
=item C<log>
3847
optional reference to array that will be filled with rule evaluation log
3848
entries.
3849
3850
=item C<nolog>
3851
optional boolean which when true disables logging to action log.
3852
3853
=back
3854
3855
Returns:
3856
3857
=over 4
3858
3859
=item C<$record>
3860
3861
new MARC record based on C<record> with C<filter> applied. If no old
3862
record for C<biblionumber> can be found, C<record> is returned unchanged.
3863
Default action when no matching filter found is to leave old record unchanged.
3864
3865
=back
3866
3867
=cut
3868
3869
sub ApplyMarcPermissions {
3870
    my $arguments     = shift;
3871
    my $biblionumber  = $arguments->{biblionumber};
3872
    my $new_record    = $arguments->{record};
3873
    my $frameworkcode = $arguments->{frameworkcode} || '';
3874
3875
    if ( !$biblionumber ) {
3876
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3877
        return;
3878
    }
3879
    if ( !$new_record ) {
3880
        carp 'ApplyMarcPermissions called on undefined record';
3881
        return;
3882
    }
3883
    my $filter = $arguments->{filter}
3884
      or return $new_record;
3885
3886
    my $log   = $arguments->{log} || [];
3887
    my $nolog = $arguments->{nolog};
3888
3889
    my $dbh = C4::Context->dbh;
3890
3891
    my $is_regex = sub {
3892
        my ( $tag, $m ) = @_;
3893
        # tag is not exactly same as possible regex
3894
        $tag ne $m &&
3895
3896
        # wildcard
3897
        $m ne '*' &&
3898
3899
        # valid tagDataType
3900
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3901
        ([1-9A-z][\dA-z]{2})$/x &&
3902
3903
        # nor valid controltagDataType
3904
        $m !~ /00[1-9A-Za-z]{1}/ &&
3905
3906
        # so we try it as a regex
3907
        $tag =~ /^$m$/
3908
    };
3909
3910
    my $old_record = GetMarcBiblio($biblionumber);
3911
    if ( $old_record ) {
3912
        my $ret_record      = MARC::Record->new();
3913
        my $perm            = GetMarcPermissions() or return $new_record;
3914
        my $marc_struct     = GetMarcStructure(1, $frameworkcode);
3915
        my $similarity_pref = C4::Context->preference("MARCPermissionsCorrectionSimilarity");
3916
        $similarity_pref    = defined $similarity_pref &&
3917
                              $similarity_pref >= 0 &&
3918
                              $similarity_pref <= 100 ?
3919
                              $similarity_pref/100.0 : undef;
3920
3921
        # First came the leader ...
3922
        $ret_record->leader($old_record->leader());
3923
3924
        # ... and then came all the tags in the framework
3925
        for my $tag (sort keys %{$marc_struct}) {
3926
            my @old_fields = $old_record->field($tag);
3927
            my @new_fields = $new_record->field($tag);
3928
            next unless @old_fields or @new_fields;
3929
            my @perms = (
3930
                $perm->{'*'},
3931
                (   # regex tags
3932
                    map { $perm->{$_} }
3933
                      grep { $is_regex->( $tag, $_ ) } sort keys %{$perm}
3934
                ),
3935
                $perm->{$tag}
3936
            );
3937
3938
            # There can be more than one field for each tag, so we have to
3939
            # iterate all fields and distinguish which are new, removed or
3940
            # modified
3941
            my $max_fields = ($#old_fields, $#new_fields)
3942
                             [$#old_fields< $#new_fields];
3943
            for ( my $i_field = 0; $i_field <= $max_fields; $i_field++ ) {
3944
                my $old_field = $old_fields[$i_field];
3945
                my $new_field = $new_fields[$i_field];
3946
3947
                # Existing field
3948
                if ( $old_field and $new_field ) {
3949
                    # Control fields
3950
                    if ( $old_field->is_control_field() ) {
3951
                        # Existing control field
3952
                        if ( $old_field and $new_field ) {
3953
                            my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @perms);
3954
                            if ( defined $on_existing->{action} ) {
3955
                                push @{$log},
3956
                                  {
3957
                                    rule         => $on_existing->{rule},
3958
                                    event        => "existing",
3959
                                    action       => $on_existing->{action},
3960
                                    tag          => $tag,
3961
                                    subfieldcode => undef,
3962
                                    filter       => $filter
3963
                                  };
3964
                                if ( $on_existing->{action} eq 'skip' ) {
3965
                                    $ret_record->insert_fields_ordered($old_field);
3966
                                    next;
3967
                                } elsif ( $on_existing->{action} eq 'overwrite' ) {
3968
                                    $ret_record->insert_fields_ordered($new_field);
3969
                                    next;
3970
                                } elsif ( $on_existing->{action} eq 'add' ) {
3971
                                    $ret_record->insert_fields_ordered($old_field);
3972
                                    $ret_record->insert_fields_ordered($new_field);
3973
                                    next;
3974
                                } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
3975
                                    if ( $similarity_pref ) {
3976
                                        my $similarity = similarity $old_field->data(), $new_field->data();
3977
                                        if ( $similarity >= $similarity_pref ) {
3978
                                            $ret_record->insert_fields_ordered($new_field);
3979
                                        } else {
3980
                                            $ret_record->insert_fields_ordered($old_field);
3981
                                            $ret_record->insert_fields_ordered($new_field);
3982
                                        }
3983
                                    } else {
3984
                                        $ret_record->insert_fields_ordered($old_field);
3985
                                    }
3986
                                }
3987
                            } else { # default to skip
3988
                                $ret_record->insert_fields_ordered($old_field);
3989
                                next;
3990
                            }
3991
                        # New control field
3992
                        } elsif ( not $old_field and $new_field ) {
3993
                            my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
3994
                            if ( defined $on_new->{action} ) {
3995
                                push @{$log},
3996
                                  {
3997
                                    rule         => $on_new->{rule},
3998
                                    event        => "new",
3999
                                    action       => $on_new->{action},
4000
                                    tag          => $tag,
4001
                                    subfieldcode => undef,
4002
                                    filter       => $filter
4003
                                  };
4004
                                if ( $on_new->{action} eq 'skip' ) { # Redundant
4005
                                    next;
4006
                                } elsif ( $on_new->{action} eq 'add' ) {
4007
                                    $ret_record->insert_fields_ordered($new_field);
4008
                                    next;
4009
                                }
4010
                            }
4011
                        # Removed control field
4012
                        } elsif ( $old_field and not $new_field ) {
4013
                            my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4014
                            if ( defined $on_removed->{action} ) {
4015
                                push @{$log},
4016
                                  {
4017
                                    rule         => $on_removed->{rule},
4018
                                    event        => "removed",
4019
                                    action       => $on_removed->{action},
4020
                                    tag          => $tag,
4021
                                    subfieldcode => undef
4022
                                  };
4023
                                if ( $on_removed->{action} eq 'skip' ) {
4024
                                    $ret_record->insert_fields_ordered($old_field);
4025
                                    next;
4026
                                } elsif ( $on_removed->{action} eq 'remove' ) {
4027
                                    next;
4028
                                }
4029
                            } else { # default to skip
4030
                                $ret_record->insert_fields_ordered($old_field);
4031
                                next;
4032
                            }
4033
                        }
4034
                    # Indicators and subfields
4035
                    } else {
4036
                        # Indicators
4037
                        my @old_ind = map { $old_field->indicator($_) } (1,2);
4038
                        my @new_ind = map { $new_field->indicator($_) } (1,2);
4039
                        my @ind = ('','');
4040
4041
                        for my $i ((0,1)) {
4042
                            my @ind_perms = (
4043
                                @perms,
4044
                                $perm->{$tag}->{subfields}->{ 'i' . ( $i + 1 ) }
4045
                            );
4046
4047
                            # Existing indicator
4048
                            if ( defined $old_ind[$i] and defined $new_ind[$i] ) {
4049
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @ind_perms);
4050
                                if ( defined $on_existing->{action} ) {
4051
                                    push @{$log},
4052
                                      {
4053
                                        rule         => $on_existing->{rule},
4054
                                        event        => "existing",
4055
                                        action       => $on_existing->{action},
4056
                                        tag          => $tag,
4057
                                        subfieldcode => 'i' . ( $i + 1 ),
4058
                                        filter       => $filter
4059
                                      };
4060
                                    if ( $on_existing->{action} eq 'skip' ) {
4061
                                        $ind[$i] = $old_ind[$i];
4062
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4063
                                        $ind[$i] = $new_ind[$i];
4064
                                    }
4065
                                } else { # default to skip
4066
                                    $ind[$i] = $old_ind[$i];
4067
                                }
4068
                            # New indicator
4069
                            } elsif ( not defined $old_ind[$i] and defined $new_ind[$i] ) {
4070
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @ind_perms);
4071
                                if ( defined $on_new->{action} ) {
4072
                                    push @{$log},
4073
                                      {
4074
                                        rule         => $on_new->{rule},
4075
                                        event        => "new",
4076
                                        action       => $on_new->{action},
4077
                                        tag          => $tag,
4078
                                        subfieldcode => 'i' . ( $i + 1 ),
4079
                                        filter       => $filter
4080
                                      };
4081
                                    if ( $on_new->{action} eq 'skip' ) {
4082
                                        $ind[$i] = $old_ind[$i];
4083
                                    } elsif ( $on_new->{action} eq 'add' ) {
4084
                                        $ind[$i] = $new_ind[$i];
4085
                                    }
4086
                                }
4087
                            # Removed indicator
4088
                            } elsif ( defined $old_ind[$i] and not defined $new_ind[$i] ) {
4089
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @ind_perms);
4090
                                if ( defined $on_removed->{action} ) {
4091
                                    push @{$log},
4092
                                      {
4093
                                        rule         => $on_removed->{rule},
4094
                                        event        => "removed",
4095
                                        action       => $on_removed->{action},
4096
                                        tag          => $tag,
4097
                                        subfieldcode => 'i' . ( $i + 1 ),
4098
                                        filter       => $filter
4099
                                      };
4100
                                    if ( $on_removed->{action} eq 'skip' ) {
4101
                                        $ind[$i] = $old_ind[$i];
4102
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4103
                                        $ind[$i] = '';
4104
                                    }
4105
                                } else { # default to skip
4106
                                    $ind[$i] = $old_ind[$i];
4107
                                }
4108
                            }
4109
                        }
4110
4111
                        # Subfields
4112
                        my @subfields = ();
4113
                        my %proccessed_subcodes = ();
4114
4115
                        # Try to preserve subfield order by first processing
4116
                        # subfields from the old record, and then subfields from
4117
                        # the framework structure unless include in old record.
4118
                        my @sorted_subfields = (
4119
                            map { shift @{$_} } $old_field->subfields(),
4120
                            () = sort keys %{ $marc_struct->{$tag} }
4121
                        );
4122
                        for my $subcode ( @sorted_subfields ) {
4123
                            next if $proccessed_subcodes{$subcode};
4124
                            $proccessed_subcodes{$subcode} = 1;
4125
                            next unless ref $marc_struct->{$tag}{$subcode} eq 'HASH';
4126
                            my @old_subfields = $old_field->subfield($subcode);
4127
                            my @new_subfields = $new_field->subfield($subcode);
4128
                            next unless @old_subfields or @new_subfields;
4129
4130
                            my @subfield_perms = (
4131
                                @perms,
4132
                                $perm->{'*'}->{subfields}->{'*'},
4133
                                $perm->{$tag}->{subfields}->{'*'},
4134
                                (    # tag->regex
4135
                                    map { $perm->{$tag}->{subfields}->{$_} }
4136
                                      grep {
4137
                                        # subcode is not exactly same as
4138
                                        # possible regex
4139
                                        $subcode ne $_ &&
4140
4141
                                        # wildcard, not checking for valid
4142
                                        # subfieldcodeDataType since it
4143
                                        # contains too many regex characters
4144
                                        $_ !~ /^(\*)$/ &&
4145
4146
                                        # so we try it as a regex
4147
                                        $subcode =~ /$_/
4148
                                      } sort
4149
                                      keys %{ $perm->{$tag}->{subfields} }
4150
                                ),
4151
                                (    # regex->*
4152
                                    map { $perm->{$_}->{subfields}->{'*'} }
4153
                                      grep { $is_regex->( $tag, $_ ) }
4154
                                      sort keys %{$perm}
4155
                                ),
4156
                                $perm->{'*'}->{subfields}->{$subcode},
4157
                                (    # regex->subcode
4158
                                    map { $perm->{$_}->{subfields}->{$subcode} }
4159
                                      grep { $is_regex->( $tag, $_ ) }
4160
                                      sort keys %{$perm}
4161
                                ),
4162
                                $perm->{$tag}->{subfields}->{$subcode}
4163
                            );
4164
4165
                            # Existing subfield
4166
                            if ( @old_subfields and @new_subfields ) {
4167
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @subfield_perms);
4168
                                if ( defined $on_existing->{action} ) {
4169
                                    push @{$log},
4170
                                      {
4171
                                        rule         => $on_existing->{rule},
4172
                                        event        => "existing",
4173
                                        action       => $on_existing->{action},
4174
                                        tag          => $tag,
4175
                                        subfieldcode => $subcode,
4176
                                        filter       => $filter
4177
                                      };
4178
                                    if ( $on_existing->{action} eq 'skip' ) {
4179
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4180
                                        next;
4181
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4182
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4183
                                        next;
4184
                                    } elsif ( $on_existing->{action} eq 'add' ) {
4185
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4186
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4187
                                        next;
4188
                                    } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
4189
                                        if ( $similarity_pref ) {
4190
                                            my @corrected_vals = ();
4191
4192
                                            # correct all old subfields
4193
                                            for ( my $i_new = 0; $i_new <= $#new_subfields; $i_new++ ) {
4194
                                                my $new_val = $new_subfields[$i_new];
4195
                                                for ( my $i_old = 0; $i_old <= $#old_subfields; $i_old++ ) {
4196
                                                    my $old_val = $old_subfields[$i_old];
4197
                                                    next if not defined $old_val or not defined $new_val;
4198
                                                    my $similarity = similarity ($old_val, $new_val);
4199
                                                    if ( $similarity >= $similarity_pref ) {
4200
                                                        $corrected_vals[$i_old] = $new_val;
4201
                                                        $new_subfields[$i_new] = undef;
4202
                                                        $old_subfields[$i_old] = undef;
4203
                                                        last;
4204
                                                    }
4205
                                                }
4206
                                            }
4207
4208
                                            # insert all unchanged old subfields
4209
                                            map {
4210
                                                $corrected_vals[$_] = $old_subfields[$_]
4211
                                                    if defined $old_subfields[$_]
4212
                                                } 0 .. $#old_subfields;
4213
4214
4215
                                            # append all remaning new subfields
4216
                                            map {push @corrected_vals, $_ if defined} @new_subfields;
4217
4218
                                            push(@subfields, map {($subcode,$_)} @corrected_vals);
4219
                                        } else {
4220
                                            push(@subfields, map {($subcode,$_)} @old_subfields);
4221
                                        }
4222
                                    }
4223
                                } else { # default to skip
4224
                                    push(@subfields, map {($subcode,$_)} @old_subfields);
4225
                                    next;
4226
                                }
4227
                            # New subfield
4228
                            } elsif ( not @old_subfields and @new_subfields ) {
4229
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @subfield_perms);
4230
                                if ( defined $on_new->{action} ) {
4231
                                    push @{$log},
4232
                                      {
4233
                                        rule         => $on_new->{rule},
4234
                                        event        => "new",
4235
                                        action       => $on_new->{action},
4236
                                        tag          => $tag,
4237
                                        subfieldcode => $subcode,
4238
                                        filter       => $filter
4239
                                      };
4240
                                    if ( $on_new->{action} eq 'skip' ) {
4241
                                        next;
4242
                                    } elsif ( $on_new->{action} eq 'add' ) {
4243
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4244
                                        next;
4245
                                    }
4246
                                }
4247
                            # Removed subfield
4248
                            } elsif ( @old_subfields and not @new_subfields ) {
4249
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @subfield_perms);
4250
                                if ( defined $on_removed->{action} ) {
4251
                                    push @{$log},
4252
                                      {
4253
                                        rule         => $on_removed->{rule},
4254
                                        event        => "removed",
4255
                                        action       => $on_removed->{action},
4256
                                        tag          => $tag,
4257
                                        subfieldcode => $subcode,
4258
                                        filter       => $filter
4259
                                      };
4260
                                    if ( $on_removed->{action} eq 'skip' ) {
4261
                                        push(@subfields, ($subcode => @old_subfields));
4262
                                        next;
4263
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4264
                                        next;
4265
                                    }
4266
                                } else { # default to skip
4267
                                    push(@subfields, ($subcode => @old_subfields));
4268
                                    next;
4269
                                }
4270
                            }
4271
                        } # / for each subfield
4272
                        $ret_record->insert_grouped_field(MARC::Field->new($tag, $ind[0], $ind[1], @subfields));
4273
                    }
4274
                # New field
4275
                } elsif ( not $old_field and $new_field ) {
4276
                    my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
4277
4278
                    if ( defined $on_new->{action} ) {
4279
                        push @{$log},
4280
                          {
4281
                            rule         => $on_new->{rule},
4282
                            event        => "new",
4283
                            action       => $on_new->{action},
4284
                            tag          => $tag,
4285
                            subfieldcode => undef,
4286
                            filter       => $filter
4287
                          };
4288
                        if ( $on_new->{action} eq 'skip' ) {
4289
                            next;
4290
                        } elsif ( $on_new->{action} eq 'add' ) {
4291
                            $ret_record->insert_fields_ordered($new_field);
4292
                            next;
4293
                        }
4294
                    }
4295
                # Removed field
4296
                } elsif ( $old_field and not $new_field ) {
4297
                    my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4298
                    if ( defined $on_removed->{action} ) {
4299
                        push @{$log},
4300
                          {
4301
                            rule         => $on_removed->{rule},
4302
                            event        => "removed",
4303
                            action       => $on_removed->{action},
4304
                            tag          => $tag,
4305
                            subfieldcode => undef,
4306
                            filter       => $filter
4307
                          };
4308
                        if ( $on_removed->{action} eq 'skip' ) {
4309
                            $ret_record->insert_grouped_field($old_field);
4310
                            next;
4311
                        } elsif ( $on_removed->{action} eq 'remove' ) {
4312
                            next;
4313
                        }
4314
                    } else { # default to skip
4315
                        $ret_record->insert_grouped_field($old_field);
4316
                        next;
4317
                    }
4318
                }
4319
            } # / for each field
4320
        } # / for each tag
4321
        if ( !$nolog && C4::Context->preference("MARCPermissionsLog") ) {
4322
            my $log_str = '';
4323
            my $n       = $#{$log};
4324
            for my $l ( @{$log} ) {
4325
                $log_str .= "{\n";
4326
                my $kn = keys %{$l};
4327
                for my $k ( sort keys %{$l} ) {
4328
                    if ( $k eq 'filter' ) {
4329
                        $log_str .= "  filter => {\n";
4330
                        my $fkn = keys %{ $l->{$k} };
4331
                        for my $fk ( sort keys %{ $l->{$k} } ) {
4332
                            $log_str .= '    '
4333
                              . $fk . ' => '
4334
                              . $l->{$k}->{$fk}
4335
                              . ( --$fkn ? ',' : '' ) . "\n";
4336
                        }
4337
                        $log_str .= "  }" . ( --$kn ? ',' : '' ) . "\n";
4338
                    }
4339
                    else {
4340
                        $log_str .= '  '
4341
                          . $k . ' => '
4342
                          . $l->{$k}
4343
                          . ( --$kn ? ',' : '' ) . "\n"
4344
                          if defined $l->{$k};
4345
                    }
4346
                }
4347
                $log_str .= '}' . ( $n-- ? ",\n" : '' );
4348
            }
4349
            logaction( "CATALOGUING", "MODIFY", $biblionumber, $log_str );
4350
        }
4351
        return $ret_record;
4352
    }
4353
    return $new_record;
4354
}
4355
4356
4357
=head2 GetMarcPermissions
4358
4359
    my $marc_permissions = GetMarcPermissions()
4360
4361
Loads MARC field permissions from the marc_permissions table.
4362
4363
Returns:
4364
4365
=over 4
4366
4367
=item C<$marc_permissions>
4368
4369
hashref with permissions structure for use with GetMarcPermissionsAction.
4370
4371
=back
4372
4373
=cut
4374
4375
sub GetMarcPermissions {
4376
    my $dbh = C4::Context->dbh;
4377
    my $rule_count = 0;
4378
    my %perms = ();
4379
4380
    my $query = '
4381
    SELECT `marc_permissions`.*,
4382
           `marc_permissions_modules`.`name`,
4383
           `marc_permissions_modules`.`description`,
4384
           `marc_permissions_modules`.`specificity`
4385
    FROM `marc_permissions`
4386
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4387
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4388
    ';
4389
    my $sth = $dbh->prepare($query);
4390
    $sth->execute();
4391
    while ( my $row = $sth->fetchrow_hashref ) {
4392
        $rule_count++;
4393
        if ( defined $row->{tagsubfield} and $row->{tagsubfield} ) {
4394
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4395
              ->{on_existing}->{ $row->{name} }->{ $row->{filter} } =
4396
              { action => $row->{on_existing}, rule => $row->{'id'} };
4397
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4398
              ->{on_new}->{ $row->{name} }->{ $row->{filter} } =
4399
              { action => $row->{on_new}, rule => $row->{'id'} };
4400
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4401
              ->{on_removed}->{ $row->{name} }->{ $row->{filter} } =
4402
              { action => $row->{on_removed}, rule => $row->{'id'} };
4403
        }
4404
        else {
4405
            $perms{ $row->{tagfield} }->{on_existing}->{ $row->{name} }
4406
              ->{ $row->{filter} } =
4407
              { action => $row->{on_existing}, rule => $row->{'id'} };
4408
            $perms{ $row->{tagfield} }->{on_new}->{ $row->{name} }
4409
              ->{ $row->{filter} } =
4410
              { action => $row->{on_new}, rule => $row->{'id'} };
4411
            $perms{ $row->{tagfield} }->{on_removed}->{ $row->{name} }
4412
              ->{ $row->{filter} } =
4413
              { action => $row->{on_removed}, rule => $row->{'id'} };
4414
        }
4415
    }
4416
4417
    return unless $rule_count;
4418
    return \%perms;
4419
}
4420
4421
=head2 GetMarcPermissionsAction
4422
4423
    my $action = GetMarcPermissionsAction($event, $filter, @permissions)
4424
4425
Gets action based on C<$event>, C<$filter> and C<@permissions>.
4426
4427
=over 4
4428
4429
=item C<$event>
4430
4431
which event: 'on_existing', 'on_new' or 'on_removed'
4432
4433
=item C<$filter>
4434
4435
hashref containing at least one filter module from the marc_permissions_modules
4436
table in form {module => filter}. Three predefined filter modules exists:
4437
4438
    * source
4439
    * category
4440
    * borrower
4441
4442
=item C<@permissions>
4443
4444
list of permission structures in order of specificity from least to most
4445
specific.
4446
4447
=back
4448
4449
Returns:
4450
4451
=over 4
4452
4453
=item C<$action>
4454
4455
hashref defining matching action.
4456
4457
=back
4458
4459
=cut
4460
4461
sub GetMarcPermissionsAction {
4462
    my $what = shift or return;
4463
    my $filter = shift or return;
4464
    my @perms = @_;
4465
    my $modules = GetMarcPermissionsModules();
4466
    my $action = undef;
4467
4468
    for my $perm (@perms) {
4469
        next if not defined $perm;
4470
        next if not defined $perm->{$what};
4471
        my $tmp_action = undef;
4472
        for my $module ( @{$modules} ) {
4473
            my $action_candidate = defined $perm->{$what}->{ $module->{'name'} } ? $perm->{$what}->{ $module->{'name'} } : undef;
4474
            if ($action_candidate) {
4475
                if (
4476
                    defined $filter->{ $module->{'name'} } and
4477
                    defined $action_candidate->{ $filter->{ $module->{'name'} } }->{action}
4478
                ) {
4479
                    $tmp_action = $perm->{$what}->{ $module->{'name'} }->{ $filter->{ $module->{'name'} } };
4480
                    last;
4481
                }
4482
                elsif (defined $action_candidate->{'*'}->{action} and !$tmp_action) {
4483
                    $tmp_action = $perm->{$what}->{ $module->{'name'} }->{'*'};
4484
                }
4485
            }
4486
        }
4487
        $action = $tmp_action if defined $tmp_action;
4488
    }
4489
    return $action;
4490
}
4491
4492
=head2 GetMarcPermissionsRules
4493
4494
    my $rules = GetMarcPermissionsRules()
4495
4496
Returns:
4497
4498
=over 4
4499
4500
=item C<$rules>
4501
4502
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
4503
table in order of module specificity and rule id.
4504
4505
=back
4506
4507
=cut
4508
4509
sub GetMarcPermissionsRules {
4510
    my $dbh = C4::Context->dbh;
4511
    my @rules = ();
4512
4513
    my $query = '
4514
    SELECT `marc_permissions`.`id`,
4515
           `marc_permissions`.`tagfield`,
4516
           `marc_permissions`.`tagsubfield`,
4517
           `marc_permissions`.`filter`,
4518
           `marc_permissions`.`on_existing`,
4519
           `marc_permissions`.`on_new`,
4520
           `marc_permissions`.`on_removed`,
4521
           `marc_permissions_modules`.`name` as  `module`,
4522
           `marc_permissions_modules`.`description`,
4523
           `marc_permissions_modules`.`specificity`
4524
    FROM `marc_permissions`
4525
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4526
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4527
    ';
4528
    my $sth = $dbh->prepare($query);
4529
    $sth->execute();
4530
    while ( my $row = $sth->fetchrow_hashref ) {
4531
        push(@rules, $row);
4532
    }
4533
4534
    return wantarray ? @rules : \@rules;
4535
}
4536
4537
=head2 GetMarcPermissionsModules
4538
4539
    my $modules = GetMarcPermissionsModules()
4540
4541
Returns:
4542
4543
=over 4
4544
4545
=item C<$modules>
4546
4547
array (in list context, arrayref otherwise) of hashrefs from
4548
marc_permissions_modules table in order of specificity.
4549
4550
=back
4551
4552
=cut
4553
4554
sub GetMarcPermissionsModules {
4555
    my $dbh = C4::Context->dbh;
4556
    my @modules = ();
4557
4558
    my $query = '
4559
    SELECT *
4560
    FROM `marc_permissions_modules`
4561
    ORDER BY `specificity` DESC
4562
    ';
4563
    my $sth = $dbh->prepare($query);
4564
    $sth->execute();
4565
    while ( my $row = $sth->fetchrow_hashref ) {
4566
        push(@modules, $row);
4567
    }
4568
4569
    return wantarray ? @modules : \@modules;
4570
}
4571
4572
=head2 ModMarcPermissionsRule
4573
4574
    my $success = ModMarcPermissionsRule($id, $fields)
4575
4576
Modifies rule in the marc_permissions table.
4577
4578
=over 4
4579
4580
=item C<$id>
4581
4582
rule id to modify
4583
4584
=item C<$fields>
4585
4586
hashref defining the table fields
4587
4588
      * tagfield - required
4589
      * tagsubfield
4590
      * module - required
4591
      * filter - required
4592
      * on_existing - required
4593
      * on_new - required
4594
      * on_removed - required
4595
4596
=back
4597
4598
Returns:
4599
4600
=over 4
4601
4602
=item C<$success>
4603
4604
undef if an error occurs, otherwise true.
4605
4606
=back
4607
4608
=cut
4609
4610
sub ModMarcPermissionsRule {
4611
    my ($id, $f) = @_;
4612
    my $dbh = C4::Context->dbh;
4613
    my $query = '
4614
    UPDATE `marc_permissions`
4615
    SET
4616
      tagfield = ?,
4617
      tagsubfield = ?,
4618
      module = ?,
4619
      filter = ?,
4620
      on_existing = ?,
4621
      on_new = ?,
4622
      on_removed = ?
4623
    WHERE
4624
      id = ?
4625
    ';
4626
    my $sth = $dbh->prepare($query);
4627
    return $sth->execute (
4628
                      $f->{tagfield},
4629
                      $f->{tagsubfield},
4630
                      $f->{module},
4631
                      $f->{filter},
4632
                      $f->{on_existing},
4633
                      $f->{on_new},
4634
                      $f->{on_removed},
4635
                      $id
4636
                  );
4637
}
4638
4639
=head2 AddMarcPermissionsRule
4640
4641
    my $success = AddMarcPermissionsRule($fields)
4642
4643
Add rule to the marc_permissions table.
4644
4645
=over 4
4646
4647
=item C<$fields>
4648
4649
hashref defining the table fields
4650
4651
      tagfield - required
4652
      tagsubfield
4653
      module - required
4654
      filter - required
4655
      on_existing - required
4656
      on_new - required
4657
      on_removed - required
4658
4659
=back
4660
4661
Returns:
4662
4663
=over 4
4664
4665
=item C<$success>
4666
4667
undef if an error occurs, otherwise true.
4668
4669
=back
4670
4671
=cut
4672
4673
sub AddMarcPermissionsRule {
4674
    my $f = shift;
4675
    my $dbh = C4::Context->dbh;
4676
    my $query = '
4677
    INSERT INTO `marc_permissions`
4678
    (
4679
      tagfield,
4680
      tagsubfield,
4681
      module,
4682
      filter,
4683
      on_existing,
4684
      on_new,
4685
      on_removed
4686
    )
4687
    VALUES (?, ?, ?, ?, ?, ?, ?)
4688
    ';
4689
    my $sth = $dbh->prepare($query);
4690
    return $sth->execute (
4691
                      $f->{tagfield},
4692
                      $f->{tagsubfield},
4693
                      $f->{module},
4694
                      $f->{filter},
4695
                      $f->{on_existing},
4696
                      $f->{on_new},
4697
                      $f->{on_removed}
4698
                  );
4699
}
4700
4701
=head2 DelMarcPermissionsRule
4702
4703
    my $success = DelMarcPermissionsRule($id)
4704
4705
Deletes rule from the marc_permissions table.
4706
4707
=over 4
4708
4709
=item C<$id>
4710
4711
rule id to delete
4712
4713
=back
4714
4715
Returns:
4716
4717
=over 4
4718
4719
=item C<$success>
4720
4721
undef if an error occurs, otherwise true.
4722
4723
=back
4724
4725
=cut
4726
4727
sub DelMarcPermissionsRule {
4728
    my $id = shift;
4729
    my $dbh = C4::Context->dbh;
4730
    my $query = '
4731
    DELETE FROM `marc_permissions`
4732
    WHERE
4733
      id = ?
4734
    ';
4735
    my $sth = $dbh->prepare($query);
4736
    return $sth->execute($id);
4737
}
3788
1;
4738
1;
3789
4739
3790
4740
(-)a/C4/Installer/PerlDependencies.pm (+5 lines)
Lines 852-857 our $PERL_DEPS = { Link Here
852
        required => 1,
852
        required => 1,
853
        min_ver => '0.28',
853
        min_ver => '0.28',
854
    },
854
    },
855
    'String::Similarity' => {
856
        usage => 'cataloguing',
857
        required => 1,
858
        min_version => '1.04',
859
    },
855
};
860
};
856
861
857
1;
862
1;
(-)a/C4/Items.pm (+2 lines)
Lines 406-411 sub AddItemBatchFromMarc { Link Here
406
        logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
406
        logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
407
407
408
        my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
408
        my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
409
410
        # @FIXME: why modify clone record here since not even saving below?
409
        $item_field->replace_with($new_item_marc->field($itemtag));
411
        $item_field->replace_with($new_item_marc->field($itemtag));
410
    }
412
    }
411
413
(-)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/cataloguing/addbiblio.pl (-1 / +9 lines)
Lines 837-843 if ( $op eq "addbiblio" ) { Link Here
837
            BiblioAutoLink( $record, $frameworkcode );
837
            BiblioAutoLink( $record, $frameworkcode );
838
        } 
838
        } 
839
        if ( $is_a_modif ) {
839
        if ( $is_a_modif ) {
840
            ModBiblio( $record, $biblionumber, $frameworkcode );
840
            my ($member) = C4::Members::GetMember('borrowernumber' => $loggedinuser);
841
            ModBiblio( $record, $biblionumber, $frameworkcode,
842
                {
843
                    source => $z3950 ? 'z39.50' : 'intranet',
844
                    category => $member->{'category_type'},
845
                    borrower => $loggedinuser
846
                }
847
            );
841
        }
848
        }
842
        else {
849
        else {
843
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
850
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
Lines 930-935 elsif ( $op eq "delete" ) { Link Here
930
    $template->param(
937
    $template->param(
931
        biblionumberdata => $biblionumber,
938
        biblionumberdata => $biblionumber,
932
        op               => $op,
939
        op               => $op,
940
        z3950            => $z3950
933
    );
941
    );
934
    if ( $op eq "duplicate" ) {
942
    if ( $op eq "duplicate" ) {
935
        $biblionumber = "";
943
        $biblionumber = "";
(-)a/help.pl (+5 lines)
Lines 24-29 use C4::Output; Link Here
24
# use C4::Auth;
24
# use C4::Auth;
25
use C4::Context;
25
use C4::Context;
26
use CGI qw ( -utf8 );
26
use CGI qw ( -utf8 );
27
use C4::Biblio;
27
28
28
sub _help_template_file_of_url {
29
sub _help_template_file_of_url {
29
    my $url = shift;
30
    my $url = shift;
Lines 75-78 if ( $help_version =~ m|^(\d+)\.(\d{2}).*$| ) { Link Here
75
}
76
}
76
$template->param( helpVersion => $help_version );
77
$template->param( helpVersion => $help_version );
77
78
79
my $rules = GetMarcPermissionsRules();
80
my $modules = GetMarcPermissionsModules();
81
$template->param( rules => $rules, modules => $modules );
82
78
output_html_with_http_headers $query, "", $template->output;
83
output_html_with_http_headers $query, "", $template->output;
(-)a/installer/data/mysql/atomicupdate/bug_14957-marc-permissions-syspref.sql (+3 lines)
Line 0 Link Here
1
INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES ('MARCPermissions','0','','Use the MARC permissions system to decide what actions to take for each field when modifying records.','YesNo');
2
INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES ('MARCPermissionsLog','0','','Write MARC permissions rule evaluations to the system log when applying rules for MARC field modifications.','YesNo');
3
INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES ('MARCPermissionsCorrectionSimilarity','90','','New field value is considered a correction if it is similar to the old to the specified percentage.','Integer');
(-)a/installer/data/mysql/atomicupdate/bug_14957-marc-permissions.sql (+31 lines)
Line 0 Link Here
1
DROP TABLE IF EXISTS `marc_permissions`;
2
DROP TABLE IF EXISTS `marc_permissions_modules`;
3
4
CREATE TABLE `marc_permissions_modules` (
5
    `id` int(11) NOT NULL auto_increment,
6
    `name` varchar(24) NOT NULL,
7
    `description` varchar(255),
8
    `specificity` int(11) NOT NULL DEFAULT 0, -- higher specificity will override rules with lower specificity
9
    PRIMARY KEY(`id`)
10
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
11
12
-- a couple of useful default filter modules
13
-- these are used in various scripts, so don't remove them if you don't know
14
-- what you're doing.
15
-- New filter modules can be added here when needed
16
INSERT INTO `marc_permissions_modules` VALUES(NULL, 'source', 'source from where modification request was sent', 0);
17
INSERT INTO `marc_permissions_modules` VALUES(NULL, 'category', 'categorycode of user who requested modification', 1);
18
INSERT INTO `marc_permissions_modules` VALUES(NULL, 'borrower', 'borrowernumber of user who requested modification', 2);
19
20
CREATE TABLE `marc_permissions` (
21
    `id` int(11) NOT NULL auto_increment,
22
    `tagfield` varchar(255) NOT NULL, -- can be regexe, so need > 3 chars
23
    `tagsubfield` varchar(255) DEFAULT NULL, -- can be regex, so need > 1 char
24
    `module` int(11) NOT NULL,
25
    `filter` varchar(255) NOT NULL,
26
    `on_existing` ENUM('skip', 'overwrite', 'add', 'add_or_correct') DEFAULT NULL,
27
    `on_new` ENUM('skip', 'add') DEFAULT NULL,
28
    `on_removed` ENUM('skip', 'remove') DEFAULT NULL,
29
    PRIMARY KEY(`id`),
30
    CONSTRAINT `marc_permissions_ibfk1` FOREIGN KEY (`module`) REFERENCES `marc_permissions_modules` (`id`) ON DELETE CASCADE
31
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc (+1 lines)
Lines 50-55 Link Here
50
    <li><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></li>
50
    <li><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></li>
51
    <li><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></li>
51
    <li><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></li>
52
    <li><a href="/cgi-bin/koha/admin/items_search_fields.pl">Item search fields</a></li>
52
    <li><a href="/cgi-bin/koha/admin/items_search_fields.pl">Item search fields</a></li>
53
    <li><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></li>
53
</ul>
54
</ul>
54
55
55
<h5>Acquisition parameters</h5>
56
<h5>Acquisition parameters</h5>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+2 lines)
Lines 89-94 Link Here
89
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt>
89
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt>
90
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd>
90
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd>
91
                    [% END %]
91
                    [% END %]
92
                    <dt><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></dt>
93
                    <dd>Managed MARC field permissions</dd>
92
                </dl>
94
                </dl>
93
95
94
                <h3>Acquisition parameters</h3>
96
                <h3>Acquisition parameters</h3>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-permissions.tt (+314 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="[% interface %]/[% theme %]/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
function doSubmit(op, id) {
17
    $('<input type="hidden"/>')
18
    .attr('name', 'op')
19
    .attr('value', op)
20
    .appendTo('#marc-permissions-form');
21
22
    if(id) {
23
        $('<input type="hidden"/>')
24
        .attr('name', 'id')
25
        .attr('value', id)
26
        .appendTo('#marc-permissions-form');
27
    }
28
29
    var valid = true;
30
    if( op == 'add' || op == 'edit') {
31
        var validate = [
32
                        $('#marc-permissions-form input[name="filter"]'),
33
                        $('#marc-permissions-form input[name="tagfield"]')
34
                       ];
35
        for(var i=0; i < validate.length; i++) {
36
            if(validate[i].val().length == 0) {
37
                validate[i].addClass('required');
38
                valid = false;
39
            } else {
40
                validate[i].removeClass('required');
41
            }
42
        }
43
    }
44
45
    if(valid ) {
46
        $('#marc-permissions-form').submit();
47
    }
48
49
    return valid;
50
}
51
52
$(document).ready(function(){
53
    $('#doremove').on("click",function(){
54
        doSubmit('doremove');
55
    });
56
    $('#doedit').on("click",function(){
57
        doSubmit('doedit', $("#doedit").attr('value'));
58
    });
59
    $('#add').on("click", function(){
60
        doSubmit('add');
61
        return false;
62
    });
63
    $('#btn_batchremove').on("click", function(){
64
        doSubmit('remove');
65
    });
66
67
    /* disable some options if subfield is indicator */
68
    $('input[name="tagsubfield"]').change(function() {
69
        if( /i[0-9]/.test($(this).val()) ) {
70
            $('select[name="on_existing"] option[value="add"]').attr('disabled', 'disabled')
71
            $('select[name="on_existing"] option[value="add_or_correct"]').attr('disabled', 'disabled')
72
        } else {
73
            $('select[name="on_existing"] option[value="add"]').removeAttr('disabled')
74
            $('select[name="on_existing"] option[value="add_or_correct"]').removeAttr('disabled')
75
        }
76
    });
77
78
    /* disable batch remove unless one or more checkboxes are checked */
79
    $('input[name="batchremove"]').change(function() {
80
        if($('input[name="batchremove"]:checked').length > 0) {
81
            $('#btn_batchremove').removeAttr('disabled');
82
        } else {
83
            $('#btn_batchremove').attr('disabled', 'disabled');
84
        }
85
    });
86
87
    /* check validity of regexes in field and subfield inputs */
88
    $('input[name="tagfield"], input[name="tagsubfield"]').change(function() {
89
        $(this).removeClass('required');
90
        $(this).attr('title', '');
91
        if($(this).val() != '*') {
92
            try {
93
                new RegExp($(this).val())
94
            } catch ( e ) {
95
                $(this).addClass('required');
96
                $(this).attr('title', 'Invalid regular expression: ' + e.message);
97
            }
98
        }
99
    });
100
101
    $.fn.dataTable.ext.order['dom-input'] = function (settings, col) {
102
        return this.api().column(col, { order: 'index' }).nodes()
103
            .map(function (td, i) {
104
                if($('input', td).val() != undefined) {
105
                    return $('input', td).val();
106
                } else if($('select', td).val() != undefined) {
107
                    return $('option[selected="selected"]', td).val();
108
                } else {
109
                    return $(td).html();
110
                }
111
            });
112
    }
113
114
    $('#marc-permissions').dataTable($.extend(true, {}, dataTablesDefaults, {
115
        "aoColumns": [
116
            {"bSearchable": false, "bSortable": false},
117
            {"sSortDataType": "dom-input"},
118
            {"sSortDataType": "dom-input"},
119
            {"bSearchable": false, "sSortDataType": "dom-input"},
120
            {"bSearchable": false, "sSortDataType": "dom-input"},
121
            {"bSearchable": false, "sSortDataType": "dom-input"},
122
            {"bSearchable": false, "sSortDataType": "dom-input"},
123
            {"bSearchable": false, "sSortDataType": "dom-input"},
124
            {"bSearchable": false, "bSortable": false},
125
            {"bSearchable": false, "bSortable": false}
126
        ],
127
        "sPaginationType": "four_button"
128
    }));
129
130
});
131
//]]>
132
</script>
133
</head>
134
<body id="admin_marc-permissions" class="admin">
135
[% INCLUDE 'header.inc' %]
136
[% INCLUDE 'cat-search.inc' %]
137
138
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a>
139
 &rsaquo; MARC field permissions
140
</div>
141
142
<div id="doc3" class="yui-t2">
143
   <div id="bd">
144
    <div id="yui-main">
145
    <div class="yui-b">
146
147
<h1>Manage MARC field permissions</h1>
148
149
[% UNLESS Koha.Preference( 'MARCPermissions' ) %]
150
    <div class="dialog message">
151
        The <b>MARCPermissions</b> preference is not set, don't forget to enable it for rules to take effect.
152
    </div>
153
[% END %]
154
[% IF removeConfirm %]
155
<div class="dialog alert">
156
<h3>Remove rule?</h3>
157
<p>Are you sure you want to remove the selected rule(s)?</p>
158
159
<form action="[% script_name %]" method="GET">
160
    <input type="submit" value="No, do not remove" class="deny"/>
161
</form>
162
<input type="button" value="Yes, remove" class="approve" id="doremove" />
163
</div>
164
[% END %]
165
166
<form action="[% script_name %]" method="POST" id="marc-permissions-form">
167
<table id="marc-permissions">
168
    <thead><tr>
169
        <th>Rule</th>
170
        <th>Tag</th>
171
        <th>Subfield tag</th>
172
        <th>Module</th>
173
        <th>Filter</th>
174
        <th>On Existing</th>
175
        <th>On New</th>
176
        <th>On Removed</th>
177
        <th>Actions</th>
178
        <th>&nbsp;</th>
179
    </tr></thead>
180
    [% UNLESS edit %]
181
    <tfoot>
182
        <tr>
183
            <th>&nbsp;</th>
184
            <th><input type="text" size="5" name="tagfield"/></th>
185
            <th><input type="text" size="5" name="tagsubfield"/></th>
186
            <th>
187
                <select name="module">
188
                    [% FOREACH module IN modules %]
189
                    <option value="[% module.id %]">[% module.name %]</option>
190
                    [% END %]
191
                </select>
192
            </th>
193
            <th><input type="text" size="5" name="filter"/></th>
194
            <th>
195
                <select name="on_existing">
196
                [% FOR on_ex IN ['skip', 'overwrite', 'add', 'add_or_correct'] %]
197
                    <option value="[% on_ex %]">[% on_ex %]</option>
198
                [% END %]
199
                </select>
200
            </th>
201
            <th>
202
                <select name="on_new">
203
                [% FOR on_new IN ['skip', 'add'] %]
204
                    <option value="[% on_new %]">[% on_new %]</option>
205
                [% END %]
206
                </select>
207
            </th>
208
            <th>
209
                <select name="on_removed">
210
                [% FOR on_removed IN ['skip', 'remove'] %]
211
                    <option value="[% on_removed %]">[% on_removed %]</option>
212
                [% END %]
213
                </select>
214
            </th>
215
            <th><button class="btn btn-small" title="Add" id="add"><i class="fa fa-plus"></i> Add rule</button></th>
216
            <th><button id="btn_batchremove" disabled="disabled" class="btn btn-small" title="Batch remove"><i class="fa fa-trash"></i> Delete selected</button></th>
217
        </tr>
218
    </tfoot>
219
    [% END %]
220
    <tbody>
221
        [% FOREACH rule IN rules %]
222
            <tr id="[% rule.id %]">
223
            [% IF rule.edit %]
224
                <td>[% rule.id %]</td>
225
                <td><input type="text" size="3" name="tagfield" value="[% rule.tagfield %]"/></td>
226
                <td><input type="text" size="1" name="tagsubfield" value="[% rule.tagsubfield %]"/></td>
227
                <td>
228
                    <select name="module">
229
                        [% FOREACH module IN modules %]
230
                            [% IF module.name == rule.module %]
231
                                <option value="[% module.id %]" selected="selected">[% module.name %]</option>
232
                            [% ELSE %]
233
                                <option value="[% module.id %]">[% module.name %]</option>
234
                            [% END %]
235
                        [% END %]
236
                    </select>
237
                </td>
238
                <td><input type="text" size="5" name="filter" value="[% rule.filter %]"/></td>
239
                <td>
240
                    <select name="on_existing">
241
                        [% FOR on_ex IN ['skip', 'overwrite', 'add', 'add_or_correct'] %]
242
                            [% IF on_ex == rule.on_existing %]
243
                                <option value="[% on_ex %]" selected="selected">[% on_ex %]</option>
244
                            [% ELSE %]
245
                                <option value="[% on_ex %]">[% on_ex %]</option>
246
                            [% END %]
247
                        [% END %]
248
                    </select>
249
                </td>
250
                <td>
251
                    <select name="on_new">
252
                        [% FOR on_new IN ['skip', 'add'] %]
253
                            [% IF on_new == rule.on_new %]
254
                                <option value="[% on_new %]" selected="selected">[% on_new %]</option>
255
                            [% ELSE %]
256
                                <option value="[% on_new %]">[% on_new %]</option>
257
                            [% END %]
258
                        [% END %]
259
                    </select>
260
                </td>
261
                <td>
262
                    <select name="on_removed">
263
                        [% FOR on_removed IN ['skip', 'remove'] %]
264
                            [% IF on_removed == rule.on_removed %]
265
                                <option value="[% on_removed %]" selected="selected">[% on_removed %]</option>
266
                            [% ELSE %]
267
                                <option value="[% on_removed %]">[% on_removed %]</option>
268
                            [% END %]
269
                        [% END %]
270
                    </select>
271
                </td>
272
                <td class="actions">
273
                    <button class="btn btn-mini" title="Save" id="doedit" value="[% rule.id %]"><i class="fa fa-check"></i> Save</button>
274
                    <a href="?"><button class="btn btn-mini" title="Cancel" ><i class="fa fa-times"></i> Cancel</button></a>
275
                </td>
276
                <td></td>
277
            [% ELSE %]
278
                <td>[% rule.id %]</td>
279
                <td>[% rule.tagfield %]</td>
280
                <td>[% rule.tagsubfield%]</td>
281
                <td>[% rule.module %]</td>
282
                <td>[% rule.filter %]</td>
283
                <td>[% rule.on_existing %]</td>
284
                <td>[% rule.on_new %]</td>
285
                <td>[% rule.on_removed %]</td>
286
                <td class="actions">
287
                    <a href="?op=remove&id=[% rule.id %]" title="Delete" class="btn btn-mini"><i class="fa fa-trash"></i> Delete</a>
288
                    <a href="?op=edit&id=[% rule.id %]" title="Edit" class="btn btn-mini"><i class="fa fa-pencil"></i> Edit</a>
289
                </td>
290
                <td>
291
                    [% IF rule.remove %]
292
                    <input type="checkbox" name="batchremove" value="[% rule.id %]" checked="checked"/>
293
                    [% ELSE %]
294
                    <input type="checkbox" name="batchremove" value="[% rule.id %]"/>
295
                    [% END %]
296
                </td>
297
            [% END %]
298
            </tr>
299
        [% END %]
300
    </tbody>
301
</table>
302
</form>
303
304
<form action="[% script_name %]" method="post">
305
<input type="hidden" name="op" value="redo-matching" />
306
</form>
307
308
</div>
309
</div>
310
<div class="yui-b">
311
[% INCLUDE 'admin-menu.inc' %]
312
</div>
313
</div>
314
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref (-21 / +9 lines)
Lines 234-257 Cataloging: Link Here
234
234
235
    Exporting:
235
    Exporting:
236
        -
236
        -
237
            - Include following fields when exporting BibTeX,
237
            - When importing records
238
            - pref: BibtexExportAdditionalFields
238
            - pref: MARCPermissions
239
              type: textarea
239
              choices:
240
            - "Use one line per tag in the format BT_TAG: TAG$SUBFIELD ( e.g. lccn: 010$a )"
240
                  yes: "use"
241
            - "<br/>"
241
                  no: "don't use"
242
            - "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] )"
242
            - MARC permissions rules to decide which action to take for each field.
243
            - "<br/>"
243
            - When <b>add_or_correct</b>, consider a field modification to be a correction if the old and new value has a similarity of
244
            - "All values of repeating tags and subfields will be printed with the given BibTeX tag."
244
            - pref: MARCPermissionsCorrectionSimilarity
245
            - "<br/>"
245
            - %. If similarity falls below this value, the new value will be treated as a new field.
246
            - "Use '@' ( with quotes ) as the BT_TAG to replace the bibtex record type with a field value of your choosing."
247
        -
248
            - Include following fields when exporting RIS,
249
            - pref: RisExportAdditionalFields
250
              type: textarea
251
            - "Use one line per tag in the format RIS_TAG: TAG$SUBFIELD ( e.g. LC: 010$a )"
252
            - "<br/>"
253
            - "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] )"
254
            - "<br/>"
255
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
256
            - "<br/>"
257
            - "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 66-71 Logging: Link Here
66
                  on: Log
66
                  on: Log
67
                  off: "Don't log"
67
                  off: "Don't log"
68
            - when reports are added, deleted or changed.
68
            - when reports are added, deleted or changed.
69
        -
70
            - pref: MARCPermissionsLog
71
              choices:
72
                  on: Log
73
                  off: "Don't log"
74
            - MARC permissions rule evaluations when applying rules for MARC field modifications.
69
    Debugging:
75
    Debugging:
70
        -
76
        -
71
            - pref: DumpTemplateVarsIntranet
77
            - pref: DumpTemplateVarsIntranet
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt (+1 lines)
Lines 545-550 function Changefwk() { Link Here
545
[% END %]
545
[% END %]
546
        <input type="hidden" name="op" value="addbiblio" />
546
        <input type="hidden" name="op" value="addbiblio" />
547
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode %]" />
547
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode %]" />
548
        <input type="hidden" name="z3950" value="[% z3950 %]" />
548
        <input type="hidden" name="biblionumber" value="[% biblionumber %]" />
549
        <input type="hidden" name="biblionumber" value="[% biblionumber %]" />
549
        <input type="hidden" name="breedingid" value="[% breedingid %]" />
550
        <input type="hidden" name="breedingid" value="[% breedingid %]" />
550
        <input type="hidden" name="changed_framework" value="" />
551
        <input type="hidden" name="changed_framework" value="" />
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/help/marc-permissions.tt (+148 lines)
Line 0 Link Here
1
[% INCLUDE 'help-top.inc' %]
2
3
<h1>Manage MARC field permissions</h1>
4
5
<h3>Rule evaluation</h3>
6
<p>Rules are evaluated from least specific to most specific. This means that a more specific rule will override a less specific rule. <b>*</b> is less specific than a regular expression. A regular expression is less specific than a normal field name (e.g. <b>245</b>). A <i>Subfield tag</i> is more specific than a <i>Tag</i>.</p>
7
<p>To add a field-specific rule (i.e. not a subfield), leave the <i>Subfield tag</i> field blank.</p>
8
9
<h4>Defaults</h4>
10
<p>Default action when no rules exist is to replace the old record with the new record. Same as if <b>MARCPermissions</b> is disabled.</p>
11
<p>Default action when no matching rule is found is to leave a field unchanged (<b>skip</b>). If you wish to changed the default actions for fields and subfields, please add wildcard rules.</p>
12
13
<h4>Wildcards</h4>
14
<p><b>*</b> can be used as a wildcard for <i>Tag</i>, <i>Subfield tag</i> and <i>Filter</i>. <b>*</b> is considered less specific than a non wildcard value, and thus will be overridden by a non wildcard value (e.g. "100", "a", etc).</p>
15
16
<h4>Regular expressions</h4>
17
<p>Regular expressions can be used in both the <i>Tag</i> and <i>Subfield tag</i> fields. Beware though, using regular expressions may create overlapping matches, in which case they will be applied in a sorted order.</p>
18
19
<h4>Example rules</h4>
20
<p>Following is an example rule set in order of specificity.</p>
21
22
<table>
23
    <thead><tr>
24
        <th>Tag</th>
25
        <th>Subfield tag</th>
26
        <th>Module</th>
27
        <th>Filter</th>
28
        <th>Description</th>
29
    </tr></thead>
30
    <tbody><tr>
31
        <td>*</td>
32
        <td></td>
33
        <td>[% modules.0.name %]</td>
34
        <td>*</td>
35
        <td><i>Match any field regardless of [% modules.0.name %]</i></td>
36
    </tr>
37
    <tr>
38
        <td>*</td>
39
        <td>*</td>
40
        <td>[% modules.0.name %]</td>
41
        <td>*</td>
42
        <td><i>Match any subfield regardless of [% modules.0.name %]</i></td>
43
    </tr>
44
    <tr>
45
        <td>245</td>
46
        <td>*</td>
47
        <td>[% modules.0.name %]</td>
48
        <td>z39.50</td>
49
        <td><i>Match any subfield under <b>245</b> when [% modules.0.name %] is <b>z39.50</b></i></td>
50
    </tr>
51
    <tr>
52
        <td>*</td>
53
        <td>b</td>
54
        <td>[% modules.0.name %]</td>
55
        <td>z39.50</td>
56
        <td><i>Match subfield <b>b</b> regardless of field when [% modules.0.name %] is <b>z39.50</b></i></td>
57
    </tr>
58
    <tr>
59
        <td>500</td>
60
        <td>[a-d]</td>
61
        <td>[% modules.0.name %]</td>
62
        <td>z39.50</td>
63
        <td><i>Match subfields <b>a, b, c, d</b> when field is 500 and [% modules.0.name %] is <b>z39.50</b></i></td>
64
    </tr>
65
    <tr>
66
        <td>5..</td>
67
        <td>a</td>
68
        <td>[% modules.0.name %]</td>
69
        <td>z39.50</td>
70
        <td><i>Match subfield <b>a</b> when field matches the regular expression <b>5..</b> (500-599) and [% modules.0.name %] is <b>z39.50</b></i></td>
71
    </tr>
72
    <tr>
73
        <td>245</td>
74
        <td>a</td>
75
        <td>[% modules.0.name %]</td>
76
        <td>z39.50</td>
77
        <td><i>Match subfield <b>a</b> when field is <b>500</b> and [% modules.0.name %] is <b>z39.50</b></i></td>
78
    </tr></tbody>
79
</table>
80
81
<br>
82
83
<h3>Available filter modules</h3>
84
<p>Filters cannot be regular expressions. Please use a single <b>*</b> as a wildcard if need.</p>
85
86
<table>
87
    <thead><tr>
88
        <th>Specificity</th>
89
        <th>Module</th>
90
        <th>Description</th>
91
    </tr></thead>
92
    <tbody>
93
    [% FOREACH module IN modules %]
94
        <tr>
95
            <td>[% module.specificity %]</td>
96
            <td>[% module.name %]</td>
97
            <td>[% module.description %]</td>
98
        </tr>
99
    [% END %]
100
    </tbody>
101
</table>
102
103
<br>
104
105
<h3>Available sources</h3>
106
<p>The following sources currently implement MARCPermissions.</p>
107
108
<table>
109
    <thead><tr>
110
        <th>Name</th>
111
        <th>Description</th>
112
    </tr></thead>
113
    <tbody><tr>
114
        <td>bulkmarcimport</td>
115
        <td>bin/migration_tools/bulkmarcimport.pl</td>
116
    </tr>
117
    <tr>
118
        <td>import_lexile</td>
119
        <td>bin/migration_tools/import_lexile.pl</td>
120
    </tr>
121
    <tr>
122
        <td>z39.50</td>
123
        <td>Import from Z39.50 search in browser</td>
124
    </tr>
125
    <tr>
126
        <td>intranet</td>
127
        <td>Modifications from intranet in browser</td>
128
    </tr>
129
    <tr>
130
        <td>batchmod</td>
131
        <td>Batch record modification in browser</td>
132
    </tr></tbody>
133
</table>
134
135
<br>
136
137
<h3>Indicators</h3>
138
<p>Indicators can be addressed as <b>i1</b> and <b>i2</b> in the <i>Subfield tag</i> field. Some actions might not be available for indicators, in which case they will be disabled.</p>
139
140
<br>
141
142
<h3>Logging</h3>
143
<p>If <b>MARCPermissionsLog</b> is enabled, log entries for each record modification will be available in the <b>Modification log</b> in the <b>Catalog</b> module under the <b>Modify</b> action. This can be very helpful when debugging rule sets.</p>
144
<p>The administration area is where you set all of your preferences for the system. Preference are broken down into several categories, detailed below.</p>
145
146
<p><strong>See the full documentation for Koha in the <a href="http://manual.koha-community.org/[% helpVersion %]/en/">manual</a> (online).</strong></p>
147
148
[% INCLUDE 'help-bottom.inc' %]
(-)a/misc/migration_tools/bulkmarcimport.pl (-1 / +1 lines)
Lines 415-421 RECORD: while ( ) { Link Here
415
			}
415
			}
416
					# create biblio, unless we already have it ( either match or isbn )
416
					# create biblio, unless we already have it ( either match or isbn )
417
            if ($biblionumber) {
417
            if ($biblionumber) {
418
                eval{$biblioitemnumber=GetBiblioData($biblionumber)->{biblioitemnumber};};
418
                eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber), { source => 'bulkmarcimport' }) };
419
                if ($update) {
419
                if ($update) {
420
                    eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
420
                    eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
421
                    if ($@) {
421
                    if ($@) {
(-)a/misc/migration_tools/import_lexile.pl (-1 / +2 lines)
Lines 151-156 while ( my $row = $csv->getline_hr($fh) ) { Link Here
151
    foreach my $biblionumber (@biblionumbers) {
151
    foreach my $biblionumber (@biblionumbers) {
152
        $counter++;
152
        $counter++;
153
        my $record = GetMarcBiblio($biblionumber);
153
        my $record = GetMarcBiblio($biblionumber);
154
        my $frameworkcode = GetFrameworkCode($biblionumber);
154
155
155
        if ($verbose) {
156
        if ($verbose) {
156
            say "Found matching record! Biblionumber: $biblionumber";
157
            say "Found matching record! Biblionumber: $biblionumber";
Lines 200-206 while ( my $row = $csv->getline_hr($fh) ) { Link Here
200
            $record->append_fields($field);
201
            $record->append_fields($field);
201
        }
202
        }
202
203
203
        ModBiblio( $record, $biblionumber ) unless ( $test );
204
        ModBiblio( $record, $biblionumber, $frameworkcode, {source => 'import_lexile'} ) unless ( $test );
204
    }
205
    }
205
206
206
}
207
}
(-)a/t/db_dependent/Biblio/MARCPermissions.t (+280 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
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
C4::Context->set_preference( 'MARCPermissions', '1');
36
C4::Context->set_preference( 'MARCPermissionsLog', '1');
37
C4::Context->set_preference( 'MARCPermissionsCorrectionSimilarity', '90');
38
39
# Create a record
40
my $record = MARC::Record->new();
41
$record->append_fields (
42
    MARC::Field->new('008', '12345'),
43
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
44
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
45
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
46
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
47
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
48
);
49
50
# Add record to DB
51
my ($biblionumber, $biblioitemnumber) = AddBiblio($record, '');
52
53
my $modules = GetMarcPermissionsModules();
54
55
##############################################################################
56
# Test overwrite rule
57
my $mod_record = MARC::Record->new();
58
$mod_record->append_fields (
59
    MARC::Field->new('008', '12345'),
60
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
61
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
62
    MARC::Field->new('500', '1','1', 'a' => 'this field has now been changed'),
63
    MARC::Field->new('500', '2','2', 'a' => 'and so have this field'),
64
);
65
66
# Clear MARC permission rules from DB
67
DelMarcPermissionsRule($_->{id}) for GetMarcPermissionsRules();
68
69
# Add MARC permission rules to DB
70
AddMarcPermissionsRule({
71
    module => $modules->[0]->{'id'},
72
    tagfield => '*',
73
    tagsubfield => '',
74
    filter => '*',
75
    on_existing => 'overwrite',
76
    on_new => 'add',
77
    on_removed => 'remove'
78
});
79
80
my @log = ();
81
my $new_record = ApplyMarcPermissions({
82
        biblionumber => $biblionumber,
83
        record => $mod_record,
84
        frameworkcode => '',
85
        filter => {$modules->[0]->{'name'} => 'foo'},
86
        log => \@log
87
    });
88
89
my @a500 = $new_record->field('500');
90
is ($a500[0]->subfield('a'), 'this field has now been changed', 'old field is replaced when overwrite');
91
is ($a500[1]->subfield('a'), 'and so have this field', 'old field is replaced when overwrite');
92
93
##############################################################################
94
# Test remove rule
95
is ($new_record->field('250'), undef, 'removed field is removed');
96
97
##############################################################################
98
# Test skip rule
99
$mod_record = MARC::Record->new();
100
$mod_record->append_fields (
101
    MARC::Field->new('008', '12345'),
102
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
103
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
104
    MARC::Field->new('500', '1','1', 'a' => 'this should not show'),
105
    MARC::Field->new('500', '2','2', 'a' => 'and neither should this'),
106
);
107
108
AddMarcPermissionsRule({
109
    module => $modules->[0]->{'id'},
110
    tagfield => '500',
111
    tagsubfield => '*',
112
    filter => '*',
113
    on_existing => 'skip',
114
    on_new => 'skip',
115
    on_removed => 'skip'
116
});
117
118
@log = ();
119
$new_record = ApplyMarcPermissions({
120
        biblionumber => $biblionumber,
121
        record => $mod_record,
122
        frameworkcode => '',
123
        filter => {$modules->[0]->{'name'} => 'foo'},
124
        log => \@log
125
    });
126
127
@a500 = $new_record->field('500');
128
is ($a500[0]->subfield('a'), 'the lazy programmer jumps over the quick brown tests', 'old field is kept when skip');
129
is ($a500[1]->subfield('a'), 'the quick brown test jumps over the lazy programmers', 'old field is kept when skip');
130
131
##############################################################################
132
# Test add rule
133
$mod_record = MARC::Record->new();
134
$mod_record->append_fields (
135
    MARC::Field->new('008', '12345'),
136
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
137
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
138
    #MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
139
    MARC::Field->new('245', '1','2', 'a' => 'some new fun value'),
140
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
141
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
142
);
143
144
AddMarcPermissionsRule({
145
    module => $modules->[0]->{'id'},
146
    tagfield => '245',
147
    tagsubfield => '*',
148
    filter => '*',
149
    on_existing => 'add',
150
    on_new => 'add',
151
    on_removed => 'skip'
152
});
153
154
155
@log = ();
156
$new_record = ApplyMarcPermissions({
157
        biblionumber => $biblionumber,
158
        record => $mod_record,
159
        frameworkcode => '',
160
        filter => {$modules->[0]->{'name'} => 'foo'},
161
        log => \@log
162
    });
163
164
my @a245 = $new_record->field('245')->subfield('a');
165
is ($a245[0], 'field data for 245 a with indicators 12', 'old field is kept when adding new');
166
is ($a245[1], 'some new fun value', 'new field is added');
167
168
##############################################################################
169
# Test add_or_correct rule
170
$mod_record = MARC::Record->new();
171
$mod_record->append_fields (
172
    MARC::Field->new('008', '12345'),
173
    #MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
174
    MARC::Field->new('100', '','', 'a' => 'a very different value'),
175
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
176
    #MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
177
    MARC::Field->new('245', '1','2', 'a' => 'Field data for 245 a with indicators 12', 'a' => 'some very different value'),
178
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
179
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
180
);
181
182
AddMarcPermissionsRule({
183
    module => $modules->[0]->{'id'},
184
    tagfield => '(100|245)',
185
    tagsubfield => '*',
186
    filter => '*',
187
    on_existing => 'add_or_correct',
188
    on_new => 'add',
189
    on_removed => 'skip'
190
});
191
192
@log = ();
193
$new_record = ApplyMarcPermissions({
194
        biblionumber => $biblionumber,
195
        record => $mod_record,
196
        frameworkcode => '',
197
        filter => {$modules->[0]->{'name'} => 'foo'},
198
        log => \@log
199
    });
200
201
@a245 = $new_record->field('245')->subfield('a');
202
is ($a245[0], 'Field data for 245 a with indicators 12', 'add_or_correct modifies field when a correction');
203
is ($a245[1], 'some very different value', 'add_or_correct adds field when not a correction');
204
205
my @a100 = $new_record->field('100')->subfield('a');
206
is ($a100[0], 'field data for 100 a without indicators', 'add_or_correct keeps old field when not a correction');
207
is ($a100[1], 'a very different value', 'add_or_correct adds field when not a correction');
208
209
##############################################################################
210
# Test rule evaluation order
211
$mod_record = MARC::Record->new();
212
$mod_record->append_fields (
213
    MARC::Field->new('008', '12345'),
214
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
215
    MARC::Field->new('250', '','', 'a' => 'take one down, pass it around'),
216
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
217
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
218
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
219
);
220
221
222
DelMarcPermissionsRule($_->{id}) for GetMarcPermissionsRules();
223
224
AddMarcPermissionsRule({
225
    module => $modules->[0]->{'id'},
226
    tagfield => '*',
227
    tagsubfield => '*',
228
    filter => '*',
229
    on_existing => 'skip',
230
    on_new => 'skip',
231
    on_removed => 'skip'
232
});
233
AddMarcPermissionsRule({
234
    module => $modules->[0]->{'id'},
235
    tagfield => '250',
236
    tagsubfield => '*',
237
    filter => '*',
238
    on_existing => 'overwrite',
239
    on_new => 'skip',
240
    on_removed => 'skip'
241
});
242
AddMarcPermissionsRule({
243
    module => $modules->[0]->{'id'},
244
    tagfield => '*',
245
    tagsubfield => 'a',
246
    filter => '*',
247
    on_existing => 'add_or_correct',
248
    on_new => 'skip',
249
    on_removed => 'skip'
250
});
251
AddMarcPermissionsRule({
252
    module => $modules->[0]->{'id'},
253
    tagfield => '250',
254
    tagsubfield => 'a',
255
    filter => '*',
256
    on_existing => 'add',
257
    on_new => 'skip',
258
    on_removed => 'skip'
259
});
260
261
@log = ();
262
$new_record = ApplyMarcPermissions({
263
        biblionumber => $biblionumber,
264
        record => $mod_record,
265
        frameworkcode => '',
266
        filter => {$modules->[0]->{'name'} => 'foo'},
267
        log => \@log
268
    });
269
270
my @rule = grep { $_->{tag} eq '250' and $_->{subfieldcode} eq 'a' } @log;
271
is(scalar @rule, 1, 'only one rule applied');
272
is($rule[0]->{event}.':'.$rule[0]->{action}, 'existing:add', 'most specific rule used');
273
274
my @a250 = $new_record->field('250')->subfield('a');
275
is ($a250[0], '250 bottles of beer on the wall', 'most specific rule is applied, original field kept');
276
is ($a250[1], 'take one down, pass it around', 'most specific rule is applied, new field added');
277
278
$dbh->rollback;
279
280
1;
(-)a/tools/batch_record_modification.pl (-4 / +10 lines)
Lines 156-162 if ( $op eq 'form' ) { Link Here
156
    my ( $job );
156
    my ( $job );
157
    if ( $runinbackground ) {
157
    if ( $runinbackground ) {
158
        my $job_size = scalar( @record_ids );
158
        my $job_size = scalar( @record_ids );
159
        $job = C4::BackgroundJob->new( $sessionID, "FIXME", '/cgi-bin/koha/tools/batch_record_modification.pl', $job_size );
159
        $job = C4::BackgroundJob->new( $sessionID, "FIXME", $ENV{SCRIPT_NAME}, $job_size );
160
        my $job_id = $job->id;
160
        my $job_id = $job->id;
161
        if (my $pid = fork) {
161
        if (my $pid = fork) {
162
            $dbh->{InactiveDestroy}  = 1;
162
            $dbh->{InactiveDestroy}  = 1;
Lines 168-174 if ( $op eq 'form' ) { Link Here
168
        } elsif (defined $pid) {
168
        } elsif (defined $pid) {
169
            close STDOUT;
169
            close STDOUT;
170
        } else {
170
        } else {
171
            warn "fork failed while attempting to run tools/batch_record_modification.pl as a background job";
171
            warn "fork failed while attempting to run $ENV{'SCRIPT_NAME'} as a background job";
172
            exit 0;
172
            exit 0;
173
        }
173
        }
174
    }
174
    }
Lines 192-198 if ( $op eq 'form' ) { Link Here
192
                my $record = GetMarcBiblio( $biblionumber );
192
                my $record = GetMarcBiblio( $biblionumber );
193
                ModifyRecordWithTemplate( $mmtid, $record );
193
                ModifyRecordWithTemplate( $mmtid, $record );
194
                my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
194
                my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
195
                ModBiblio( $record, $biblionumber, $frameworkcode );
195
                my ($member) = C4::Members::GetMember('borrowernumber' => $loggedinuser);
196
                ModBiblio( $record, $biblionumber, $frameworkcode,
197
                    {
198
                        source => 'batchmod',
199
                        category => $member->{'category_type'},
200
                        borrower => $loggedinuser
201
                    }
202
                );
196
            };
203
            };
197
            if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
204
            if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
198
                push @messages, {
205
                push @messages, {
199
- 

Return to bug 14957