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 2656-2662 sub TransformHtmlToMarc { Link Here
2656
2664
2657
=head2 TransformMarcToKoha
2665
=head2 TransformMarcToKoha
2658
2666
2659
  $result = TransformMarcToKoha( $record, $frameworkcode )
2667
  $result = TransformMarcToKoha( $record, $frameworkcode, $biblionumber, $filter )
2660
2668
2661
Extract data from a MARC bib record into a hashref representing
2669
Extract data from a MARC bib record into a hashref representing
2662
Koha biblio, biblioitems, and items fields. 
2670
Koha biblio, biblioitems, and items fields. 
Lines 2667-2673 hash_ref Link Here
2667
=cut
2675
=cut
2668
2676
2669
sub TransformMarcToKoha {
2677
sub TransformMarcToKoha {
2670
    my ( $record, $frameworkcode, $limit_table ) = @_;
2678
    my ( $record, $frameworkcode, $limit_table, $biblionumber, $filter ) = @_;
2671
2679
2672
    my $result = {};
2680
    my $result = {};
2673
    if (!defined $record) {
2681
    if (!defined $record) {
Lines 2688-2693 sub TransformMarcToKoha { Link Here
2688
        $tables{'biblioitems'} = 1;
2696
        $tables{'biblioitems'} = 1;
2689
    }
2697
    }
2690
2698
2699
    # apply permissions
2700
    if ( C4::Context->preference('MARCPermissions') && $biblionumber && $filter) {
2701
        $record = ApplyMarcPermissions({
2702
                biblionumber => $biblionumber,
2703
                record => $record,
2704
                frameworkcode => $frameworkcode,
2705
                filter => $filter,
2706
                nolog => 1
2707
            });
2708
    }
2709
2691
    # traverse through record
2710
    # traverse through record
2692
  MARCFIELD: foreach my $field ( $record->fields() ) {
2711
  MARCFIELD: foreach my $field ( $record->fields() ) {
2693
        my $tag = $field->tag();
2712
        my $tag = $field->tag();
Lines 3442-3448 sub _koha_delete_biblioitems { Link Here
3442
3461
3443
=head2 ModBiblioMarc
3462
=head2 ModBiblioMarc
3444
3463
3445
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3464
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode,$filter);
3446
3465
3447
Add MARC XML data for a biblio to koha
3466
Add MARC XML data for a biblio to koha
3448
3467
Lines 3453-3459 Function exported, but should NOT be used, unless you really know what you're do Link Here
3453
sub ModBiblioMarc {
3472
sub ModBiblioMarc {
3454
    # pass the MARC::Record to this function, and it will create the records in
3473
    # pass the MARC::Record to this function, and it will create the records in
3455
    # the marcxml field
3474
    # the marcxml field
3456
    my ( $record, $biblionumber, $frameworkcode ) = @_;
3475
    my ( $record, $biblionumber, $frameworkcode, $filter ) = @_;
3476
3457
    if ( !$record ) {
3477
    if ( !$record ) {
3458
        carp 'ModBiblioMarc passed an undefined record';
3478
        carp 'ModBiblioMarc passed an undefined record';
3459
        return;
3479
        return;
Lines 3466-3471 sub ModBiblioMarc { Link Here
3466
    if ( !$frameworkcode ) {
3486
    if ( !$frameworkcode ) {
3467
        $frameworkcode = "";
3487
        $frameworkcode = "";
3468
    }
3488
    }
3489
3490
    # apply permissions
3491
    if ( C4::Context->preference('MARCPermissions') ) {
3492
        $record = ApplyMarcPermissions({
3493
                biblionumber => $biblionumber,
3494
                record => $record,
3495
                frameworkcode => $frameworkcode,
3496
                filter => $filter
3497
            });
3498
    }
3499
3469
    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3500
    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3470
    $sth->execute( $frameworkcode, $biblionumber );
3501
    $sth->execute( $frameworkcode, $biblionumber );
3471
    $sth->finish;
3502
    $sth->finish;
Lines 3786-3791 sub RemoveAllNsb { Link Here
3786
    return $record;
3817
    return $record;
3787
}
3818
}
3788
3819
3820
=head2 ApplyMarcPermissions
3821
3822
    my $record = ApplyMarcPermissions($arguments)
3823
3824
Applies marc permission rules to a record.
3825
3826
C<$arguments> is expected to be a hashref with below keys defined.
3827
3828
=over 4
3829
3830
=item C<biblionumber>
3831
biblionumber of old record
3832
3833
=item C<record>
3834
record that will modify old record
3835
3836
=item C<frameworkcode>
3837
only tags included in framework will be processed
3838
3839
=item C<filter>
3840
hashref containing at least one filter module from the marc_permissions_modules
3841
table in form {module => filter}. Three predefined filter modules exists:
3842
3843
    * source
3844
    * category
3845
    * borrower
3846
3847
=item C<log>
3848
optional reference to array that will be filled with rule evaluation log
3849
entries.
3850
3851
=item C<nolog>
3852
optional boolean which when true disables logging to action log.
3853
3854
=back
3855
3856
Returns:
3857
3858
=over 4
3859
3860
=item C<$record>
3861
3862
new MARC record based on C<record> with C<filter> applied. If no old
3863
record for C<biblionumber> can be found, C<record> is returned unchanged.
3864
Default action when no matching filter found is to leave old record unchanged.
3865
3866
=back
3867
3868
=cut
3869
3870
sub ApplyMarcPermissions {
3871
    my $arguments     = shift;
3872
    my $biblionumber  = $arguments->{biblionumber};
3873
    my $new_record    = $arguments->{record};
3874
    my $frameworkcode = $arguments->{frameworkcode} || '';
3875
3876
    if ( !$biblionumber ) {
3877
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3878
        return;
3879
    }
3880
    if ( !$new_record ) {
3881
        carp 'ApplyMarcPermissions called on undefined record';
3882
        return;
3883
    }
3884
    my $filter = $arguments->{filter}
3885
      or return $new_record;
3886
3887
    my $log   = $arguments->{log} || [];
3888
    my $nolog = $arguments->{nolog};
3889
3890
    my $dbh = C4::Context->dbh;
3891
3892
    my $is_regex = sub {
3893
        my ( $tag, $m ) = @_;
3894
        # tag is not exactly same as possible regex
3895
        $tag ne $m &&
3896
3897
        # wildcard
3898
        $m ne '*' &&
3899
3900
        # valid tagDataType
3901
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3902
        ([1-9A-z][\dA-z]{2})$/x &&
3903
3904
        # nor valid controltagDataType
3905
        $m !~ /00[1-9A-Za-z]{1}/ &&
3906
3907
        # so we try it as a regex
3908
        $tag =~ /^$m$/
3909
    };
3910
3911
    my $old_record = GetMarcBiblio($biblionumber);
3912
    if ( $old_record ) {
3913
        my $ret_record      = MARC::Record->new();
3914
        my $perm            = GetMarcPermissions() or return $new_record;
3915
        my $marc_struct     = GetMarcStructure(1, $frameworkcode);
3916
        my $similarity_pref = C4::Context->preference("MARCPermissionsCorrectionSimilarity");
3917
        $similarity_pref    = defined $similarity_pref &&
3918
                              $similarity_pref >= 0 &&
3919
                              $similarity_pref <= 100 ?
3920
                              $similarity_pref/100.0 : undef;
3921
3922
        # First came the leader ...
3923
        $ret_record->leader($old_record->leader());
3924
3925
        # ... and then came all the tags in the framework
3926
        for my $tag (sort keys %{$marc_struct}) {
3927
            my @old_fields = $old_record->field($tag);
3928
            my @new_fields = $new_record->field($tag);
3929
            next unless @old_fields or @new_fields;
3930
            my @perms = (
3931
                $perm->{'*'},
3932
                (   # regex tags
3933
                    map { $perm->{$_} }
3934
                      grep { $is_regex->( $tag, $_ ) } sort keys %{$perm}
3935
                ),
3936
                $perm->{$tag}
3937
            );
3938
3939
            # There can be more than one field for each tag, so we have to
3940
            # iterate all fields and distinguish which are new, removed or
3941
            # modified
3942
            my $max_fields = ($#old_fields, $#new_fields)
3943
                             [$#old_fields< $#new_fields];
3944
            for ( my $i_field = 0; $i_field <= $max_fields; $i_field++ ) {
3945
                my $old_field = $old_fields[$i_field];
3946
                my $new_field = $new_fields[$i_field];
3947
3948
                # Existing field
3949
                if ( $old_field and $new_field ) {
3950
                    # Control fields
3951
                    if ( $old_field->is_control_field() ) {
3952
                        # Existing control field
3953
                        if ( $old_field and $new_field ) {
3954
                            my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @perms);
3955
                            if ( defined $on_existing->{action} ) {
3956
                                push @{$log},
3957
                                  {
3958
                                    rule         => $on_existing->{rule},
3959
                                    event        => "existing",
3960
                                    action       => $on_existing->{action},
3961
                                    tag          => $tag,
3962
                                    subfieldcode => undef,
3963
                                    filter       => $filter
3964
                                  };
3965
                                if ( $on_existing->{action} eq 'skip' ) {
3966
                                    $ret_record->insert_fields_ordered($old_field);
3967
                                    next;
3968
                                } elsif ( $on_existing->{action} eq 'overwrite' ) {
3969
                                    $ret_record->insert_fields_ordered($new_field);
3970
                                    next;
3971
                                } elsif ( $on_existing->{action} eq 'add' ) {
3972
                                    $ret_record->insert_fields_ordered($old_field);
3973
                                    $ret_record->insert_fields_ordered($new_field);
3974
                                    next;
3975
                                } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
3976
                                    if ( $similarity_pref ) {
3977
                                        my $similarity = similarity $old_field->data(), $new_field->data();
3978
                                        if ( $similarity >= $similarity_pref ) {
3979
                                            $ret_record->insert_fields_ordered($new_field);
3980
                                        } else {
3981
                                            $ret_record->insert_fields_ordered($old_field);
3982
                                            $ret_record->insert_fields_ordered($new_field);
3983
                                        }
3984
                                    } else {
3985
                                        $ret_record->insert_fields_ordered($old_field);
3986
                                    }
3987
                                }
3988
                            } else { # default to skip
3989
                                $ret_record->insert_fields_ordered($old_field);
3990
                                next;
3991
                            }
3992
                        # New control field
3993
                        } elsif ( not $old_field and $new_field ) {
3994
                            my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
3995
                            if ( defined $on_new->{action} ) {
3996
                                push @{$log},
3997
                                  {
3998
                                    rule         => $on_new->{rule},
3999
                                    event        => "new",
4000
                                    action       => $on_new->{action},
4001
                                    tag          => $tag,
4002
                                    subfieldcode => undef,
4003
                                    filter       => $filter
4004
                                  };
4005
                                if ( $on_new->{action} eq 'skip' ) { # Redundant
4006
                                    next;
4007
                                } elsif ( $on_new->{action} eq 'add' ) {
4008
                                    $ret_record->insert_fields_ordered($new_field);
4009
                                    next;
4010
                                }
4011
                            }
4012
                        # Removed control field
4013
                        } elsif ( $old_field and not $new_field ) {
4014
                            my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4015
                            if ( defined $on_removed->{action} ) {
4016
                                push @{$log},
4017
                                  {
4018
                                    rule         => $on_removed->{rule},
4019
                                    event        => "removed",
4020
                                    action       => $on_removed->{action},
4021
                                    tag          => $tag,
4022
                                    subfieldcode => undef
4023
                                  };
4024
                                if ( $on_removed->{action} eq 'skip' ) {
4025
                                    $ret_record->insert_fields_ordered($old_field);
4026
                                    next;
4027
                                } elsif ( $on_removed->{action} eq 'remove' ) {
4028
                                    next;
4029
                                }
4030
                            } else { # default to skip
4031
                                $ret_record->insert_fields_ordered($old_field);
4032
                                next;
4033
                            }
4034
                        }
4035
                    # Indicators and subfields
4036
                    } else {
4037
                        # Indicators
4038
                        my @old_ind = map { $old_field->indicator($_) } (1,2);
4039
                        my @new_ind = map { $new_field->indicator($_) } (1,2);
4040
                        my @ind = ('','');
4041
4042
                        for my $i ((0,1)) {
4043
                            my @ind_perms = (
4044
                                @perms,
4045
                                $perm->{$tag}->{subfields}->{ 'i' . ( $i + 1 ) }
4046
                            );
4047
4048
                            # Existing indicator
4049
                            if ( defined $old_ind[$i] and defined $new_ind[$i] ) {
4050
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @ind_perms);
4051
                                if ( defined $on_existing->{action} ) {
4052
                                    push @{$log},
4053
                                      {
4054
                                        rule         => $on_existing->{rule},
4055
                                        event        => "existing",
4056
                                        action       => $on_existing->{action},
4057
                                        tag          => $tag,
4058
                                        subfieldcode => 'i' . ( $i + 1 ),
4059
                                        filter       => $filter
4060
                                      };
4061
                                    if ( $on_existing->{action} eq 'skip' ) {
4062
                                        $ind[$i] = $old_ind[$i];
4063
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4064
                                        $ind[$i] = $new_ind[$i];
4065
                                    }
4066
                                } else { # default to skip
4067
                                    $ind[$i] = $old_ind[$i];
4068
                                }
4069
                            # New indicator
4070
                            } elsif ( not defined $old_ind[$i] and defined $new_ind[$i] ) {
4071
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @ind_perms);
4072
                                if ( defined $on_new->{action} ) {
4073
                                    push @{$log},
4074
                                      {
4075
                                        rule         => $on_new->{rule},
4076
                                        event        => "new",
4077
                                        action       => $on_new->{action},
4078
                                        tag          => $tag,
4079
                                        subfieldcode => 'i' . ( $i + 1 ),
4080
                                        filter       => $filter
4081
                                      };
4082
                                    if ( $on_new->{action} eq 'skip' ) {
4083
                                        $ind[$i] = $old_ind[$i];
4084
                                    } elsif ( $on_new->{action} eq 'add' ) {
4085
                                        $ind[$i] = $new_ind[$i];
4086
                                    }
4087
                                }
4088
                            # Removed indicator
4089
                            } elsif ( defined $old_ind[$i] and not defined $new_ind[$i] ) {
4090
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @ind_perms);
4091
                                if ( defined $on_removed->{action} ) {
4092
                                    push @{$log},
4093
                                      {
4094
                                        rule         => $on_removed->{rule},
4095
                                        event        => "removed",
4096
                                        action       => $on_removed->{action},
4097
                                        tag          => $tag,
4098
                                        subfieldcode => 'i' . ( $i + 1 ),
4099
                                        filter       => $filter
4100
                                      };
4101
                                    if ( $on_removed->{action} eq 'skip' ) {
4102
                                        $ind[$i] = $old_ind[$i];
4103
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4104
                                        $ind[$i] = '';
4105
                                    }
4106
                                } else { # default to skip
4107
                                    $ind[$i] = $old_ind[$i];
4108
                                }
4109
                            }
4110
                        }
4111
4112
                        # Subfields
4113
                        my @subfields = ();
4114
                        my %proccessed_subcodes = ();
4115
4116
                        # Try to preserve subfield order by first processing
4117
                        # subfields from the old record, and then subfields from
4118
                        # the framework structure unless include in old record.
4119
                        my @sorted_subfields = (
4120
                            map { shift @{$_} } $old_field->subfields(),
4121
                            () = sort keys %{ $marc_struct->{$tag} }
4122
                        );
4123
                        for my $subcode ( @sorted_subfields ) {
4124
                            next if $proccessed_subcodes{$subcode};
4125
                            $proccessed_subcodes{$subcode} = 1;
4126
                            next unless ref $marc_struct->{$tag}{$subcode} eq 'HASH';
4127
                            my @old_subfields = $old_field->subfield($subcode);
4128
                            my @new_subfields = $new_field->subfield($subcode);
4129
                            next unless @old_subfields or @new_subfields;
4130
4131
                            my @subfield_perms = (
4132
                                @perms,
4133
                                $perm->{'*'}->{subfields}->{'*'},
4134
                                $perm->{$tag}->{subfields}->{'*'},
4135
                                (    # tag->regex
4136
                                    map { $perm->{$tag}->{subfields}->{$_} }
4137
                                      grep {
4138
                                        # subcode is not exactly same as
4139
                                        # possible regex
4140
                                        $subcode ne $_ &&
4141
4142
                                        # wildcard, not checking for valid
4143
                                        # subfieldcodeDataType since it
4144
                                        # contains too many regex characters
4145
                                        $_ !~ /^(\*)$/ &&
4146
4147
                                        # so we try it as a regex
4148
                                        $subcode =~ /$_/
4149
                                      } sort
4150
                                      keys %{ $perm->{$tag}->{subfields} }
4151
                                ),
4152
                                (    # regex->*
4153
                                    map { $perm->{$_}->{subfields}->{'*'} }
4154
                                      grep { $is_regex->( $tag, $_ ) }
4155
                                      sort keys %{$perm}
4156
                                ),
4157
                                $perm->{'*'}->{subfields}->{$subcode},
4158
                                (    # regex->subcode
4159
                                    map { $perm->{$_}->{subfields}->{$subcode} }
4160
                                      grep { $is_regex->( $tag, $_ ) }
4161
                                      sort keys %{$perm}
4162
                                ),
4163
                                $perm->{$tag}->{subfields}->{$subcode}
4164
                            );
4165
4166
                            # Existing subfield
4167
                            if ( @old_subfields and @new_subfields ) {
4168
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @subfield_perms);
4169
                                if ( defined $on_existing->{action} ) {
4170
                                    push @{$log},
4171
                                      {
4172
                                        rule         => $on_existing->{rule},
4173
                                        event        => "existing",
4174
                                        action       => $on_existing->{action},
4175
                                        tag          => $tag,
4176
                                        subfieldcode => $subcode,
4177
                                        filter       => $filter
4178
                                      };
4179
                                    if ( $on_existing->{action} eq 'skip' ) {
4180
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4181
                                        next;
4182
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4183
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4184
                                        next;
4185
                                    } elsif ( $on_existing->{action} eq 'add' ) {
4186
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4187
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4188
                                        next;
4189
                                    } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
4190
                                        if ( $similarity_pref ) {
4191
                                            my @corrected_vals = ();
4192
4193
                                            # correct all old subfields
4194
                                            for ( my $i_new = 0; $i_new <= $#new_subfields; $i_new++ ) {
4195
                                                my $new_val = $new_subfields[$i_new];
4196
                                                for ( my $i_old = 0; $i_old <= $#old_subfields; $i_old++ ) {
4197
                                                    my $old_val = $old_subfields[$i_old];
4198
                                                    next if not defined $old_val or not defined $new_val;
4199
                                                    my $similarity = similarity ($old_val, $new_val);
4200
                                                    if ( $similarity >= $similarity_pref ) {
4201
                                                        $corrected_vals[$i_old] = $new_val;
4202
                                                        $new_subfields[$i_new] = undef;
4203
                                                        $old_subfields[$i_old] = undef;
4204
                                                        last;
4205
                                                    }
4206
                                                }
4207
                                            }
4208
4209
                                            # insert all unchanged old subfields
4210
                                            map {
4211
                                                $corrected_vals[$_] = $old_subfields[$_]
4212
                                                    if defined $old_subfields[$_]
4213
                                                } 0 .. $#old_subfields;
4214
4215
4216
                                            # append all remaning new subfields
4217
                                            map {push @corrected_vals, $_ if defined} @new_subfields;
4218
4219
                                            push(@subfields, map {($subcode,$_)} @corrected_vals);
4220
                                        } else {
4221
                                            push(@subfields, map {($subcode,$_)} @old_subfields);
4222
                                        }
4223
                                    }
4224
                                } else { # default to skip
4225
                                    push(@subfields, map {($subcode,$_)} @old_subfields);
4226
                                    next;
4227
                                }
4228
                            # New subfield
4229
                            } elsif ( not @old_subfields and @new_subfields ) {
4230
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @subfield_perms);
4231
                                if ( defined $on_new->{action} ) {
4232
                                    push @{$log},
4233
                                      {
4234
                                        rule         => $on_new->{rule},
4235
                                        event        => "new",
4236
                                        action       => $on_new->{action},
4237
                                        tag          => $tag,
4238
                                        subfieldcode => $subcode,
4239
                                        filter       => $filter
4240
                                      };
4241
                                    if ( $on_new->{action} eq 'skip' ) {
4242
                                        next;
4243
                                    } elsif ( $on_new->{action} eq 'add' ) {
4244
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4245
                                        next;
4246
                                    }
4247
                                }
4248
                            # Removed subfield
4249
                            } elsif ( @old_subfields and not @new_subfields ) {
4250
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @subfield_perms);
4251
                                if ( defined $on_removed->{action} ) {
4252
                                    push @{$log},
4253
                                      {
4254
                                        rule         => $on_removed->{rule},
4255
                                        event        => "removed",
4256
                                        action       => $on_removed->{action},
4257
                                        tag          => $tag,
4258
                                        subfieldcode => $subcode,
4259
                                        filter       => $filter
4260
                                      };
4261
                                    if ( $on_removed->{action} eq 'skip' ) {
4262
                                        push(@subfields, ($subcode => @old_subfields));
4263
                                        next;
4264
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4265
                                        next;
4266
                                    }
4267
                                } else { # default to skip
4268
                                    push(@subfields, ($subcode => @old_subfields));
4269
                                    next;
4270
                                }
4271
                            }
4272
                        } # / for each subfield
4273
                        $ret_record->insert_grouped_field(MARC::Field->new($tag, $ind[0], $ind[1], @subfields));
4274
                    }
4275
                # New field
4276
                } elsif ( not $old_field and $new_field ) {
4277
                    my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
4278
4279
                    if ( defined $on_new->{action} ) {
4280
                        push @{$log},
4281
                          {
4282
                            rule         => $on_new->{rule},
4283
                            event        => "new",
4284
                            action       => $on_new->{action},
4285
                            tag          => $tag,
4286
                            subfieldcode => undef,
4287
                            filter       => $filter
4288
                          };
4289
                        if ( $on_new->{action} eq 'skip' ) {
4290
                            next;
4291
                        } elsif ( $on_new->{action} eq 'add' ) {
4292
                            $ret_record->insert_fields_ordered($new_field);
4293
                            next;
4294
                        }
4295
                    }
4296
                # Removed field
4297
                } elsif ( $old_field and not $new_field ) {
4298
                    my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4299
                    if ( defined $on_removed->{action} ) {
4300
                        push @{$log},
4301
                          {
4302
                            rule         => $on_removed->{rule},
4303
                            event        => "removed",
4304
                            action       => $on_removed->{action},
4305
                            tag          => $tag,
4306
                            subfieldcode => undef,
4307
                            filter       => $filter
4308
                          };
4309
                        if ( $on_removed->{action} eq 'skip' ) {
4310
                            $ret_record->insert_grouped_field($old_field);
4311
                            next;
4312
                        } elsif ( $on_removed->{action} eq 'remove' ) {
4313
                            next;
4314
                        }
4315
                    } else { # default to skip
4316
                        $ret_record->insert_grouped_field($old_field);
4317
                        next;
4318
                    }
4319
                }
4320
            } # / for each field
4321
        } # / for each tag
4322
        if ( !$nolog && C4::Context->preference("MARCPermissionsLog") ) {
4323
            my $log_str = '';
4324
            my $n       = $#{$log};
4325
            for my $l ( @{$log} ) {
4326
                $log_str .= "{\n";
4327
                my $kn = keys %{$l};
4328
                for my $k ( sort keys %{$l} ) {
4329
                    if ( $k eq 'filter' ) {
4330
                        $log_str .= "  filter => {\n";
4331
                        my $fkn = keys %{ $l->{$k} };
4332
                        for my $fk ( sort keys %{ $l->{$k} } ) {
4333
                            $log_str .= '    '
4334
                              . $fk . ' => '
4335
                              . $l->{$k}->{$fk}
4336
                              . ( --$fkn ? ',' : '' ) . "\n";
4337
                        }
4338
                        $log_str .= "  }" . ( --$kn ? ',' : '' ) . "\n";
4339
                    }
4340
                    else {
4341
                        $log_str .= '  '
4342
                          . $k . ' => '
4343
                          . $l->{$k}
4344
                          . ( --$kn ? ',' : '' ) . "\n"
4345
                          if defined $l->{$k};
4346
                    }
4347
                }
4348
                $log_str .= '}' . ( $n-- ? ",\n" : '' );
4349
            }
4350
            logaction( "CATALOGUING", "MODIFY", $biblionumber, $log_str );
4351
        }
4352
        return $ret_record;
4353
    }
4354
    return $new_record;
4355
}
4356
4357
4358
=head2 GetMarcPermissions
4359
4360
    my $marc_permissions = GetMarcPermissions()
4361
4362
Loads MARC field permissions from the marc_permissions table.
4363
4364
Returns:
4365
4366
=over 4
4367
4368
=item C<$marc_permissions>
4369
4370
hashref with permissions structure for use with GetMarcPermissionsAction.
4371
4372
=back
4373
4374
=cut
4375
4376
sub GetMarcPermissions {
4377
    my $dbh = C4::Context->dbh;
4378
    my $rule_count = 0;
4379
    my %perms = ();
4380
4381
    my $query = '
4382
    SELECT `marc_permissions`.*,
4383
           `marc_permissions_modules`.`name`,
4384
           `marc_permissions_modules`.`description`,
4385
           `marc_permissions_modules`.`specificity`
4386
    FROM `marc_permissions`
4387
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4388
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4389
    ';
4390
    my $sth = $dbh->prepare($query);
4391
    $sth->execute();
4392
    while ( my $row = $sth->fetchrow_hashref ) {
4393
        $rule_count++;
4394
        if ( defined $row->{tagsubfield} and $row->{tagsubfield} ) {
4395
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4396
              ->{on_existing}->{ $row->{name} }->{ $row->{filter} } =
4397
              { action => $row->{on_existing}, rule => $row->{'id'} };
4398
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4399
              ->{on_new}->{ $row->{name} }->{ $row->{filter} } =
4400
              { action => $row->{on_new}, rule => $row->{'id'} };
4401
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4402
              ->{on_removed}->{ $row->{name} }->{ $row->{filter} } =
4403
              { action => $row->{on_removed}, rule => $row->{'id'} };
4404
        }
4405
        else {
4406
            $perms{ $row->{tagfield} }->{on_existing}->{ $row->{name} }
4407
              ->{ $row->{filter} } =
4408
              { action => $row->{on_existing}, rule => $row->{'id'} };
4409
            $perms{ $row->{tagfield} }->{on_new}->{ $row->{name} }
4410
              ->{ $row->{filter} } =
4411
              { action => $row->{on_new}, rule => $row->{'id'} };
4412
            $perms{ $row->{tagfield} }->{on_removed}->{ $row->{name} }
4413
              ->{ $row->{filter} } =
4414
              { action => $row->{on_removed}, rule => $row->{'id'} };
4415
        }
4416
    }
4417
4418
    return unless $rule_count;
4419
    return \%perms;
4420
}
4421
4422
=head2 GetMarcPermissionsAction
4423
4424
    my $action = GetMarcPermissionsAction($event, $filter, @permissions)
4425
4426
Gets action based on C<$event>, C<$filter> and C<@permissions>.
4427
4428
=over 4
4429
4430
=item C<$event>
4431
4432
which event: 'on_existing', 'on_new' or 'on_removed'
4433
4434
=item C<$filter>
4435
4436
hashref containing at least one filter module from the marc_permissions_modules
4437
table in form {module => filter}. Three predefined filter modules exists:
4438
4439
    * source
4440
    * category
4441
    * borrower
4442
4443
=item C<@permissions>
4444
4445
list of permission structures in order of specificity from least to most
4446
specific.
4447
4448
=back
4449
4450
Returns:
4451
4452
=over 4
4453
4454
=item C<$action>
4455
4456
hashref defining matching action.
4457
4458
=back
4459
4460
=cut
4461
4462
sub GetMarcPermissionsAction {
4463
    my $what = shift or return;
4464
    my $filter = shift or return;
4465
    my @perms = @_;
4466
    my $modules = GetMarcPermissionsModules();
4467
    my $action = undef;
4468
4469
    for my $perm (@perms) {
4470
        next if not defined $perm;
4471
        next if not defined $perm->{$what};
4472
        my $tmp_action = undef;
4473
        for my $module ( @{$modules} ) {
4474
            my $action_candidate = defined $perm->{$what}->{ $module->{'name'} } ? $perm->{$what}->{ $module->{'name'} } : undef;
4475
            if ($action_candidate) {
4476
                if (
4477
                    defined $filter->{ $module->{'name'} } and
4478
                    defined $action_candidate->{ $filter->{ $module->{'name'} } }->{action}
4479
                ) {
4480
                    $tmp_action = $perm->{$what}->{ $module->{'name'} }->{ $filter->{ $module->{'name'} } };
4481
                    last;
4482
                }
4483
                elsif (defined $action_candidate->{'*'}->{action} and !$tmp_action) {
4484
                    $tmp_action = $perm->{$what}->{ $module->{'name'} }->{'*'};
4485
                }
4486
            }
4487
        }
4488
        $action = $tmp_action if defined $tmp_action;
4489
    }
4490
    return $action;
4491
}
4492
4493
=head2 GetMarcPermissionsRules
4494
4495
    my $rules = GetMarcPermissionsRules()
4496
4497
Returns:
4498
4499
=over 4
4500
4501
=item C<$rules>
4502
4503
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
4504
table in order of module specificity and rule id.
4505
4506
=back
4507
4508
=cut
4509
4510
sub GetMarcPermissionsRules {
4511
    my $dbh = C4::Context->dbh;
4512
    my @rules = ();
4513
4514
    my $query = '
4515
    SELECT `marc_permissions`.`id`,
4516
           `marc_permissions`.`tagfield`,
4517
           `marc_permissions`.`tagsubfield`,
4518
           `marc_permissions`.`filter`,
4519
           `marc_permissions`.`on_existing`,
4520
           `marc_permissions`.`on_new`,
4521
           `marc_permissions`.`on_removed`,
4522
           `marc_permissions_modules`.`name` as  `module`,
4523
           `marc_permissions_modules`.`description`,
4524
           `marc_permissions_modules`.`specificity`
4525
    FROM `marc_permissions`
4526
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4527
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4528
    ';
4529
    my $sth = $dbh->prepare($query);
4530
    $sth->execute();
4531
    while ( my $row = $sth->fetchrow_hashref ) {
4532
        push(@rules, $row);
4533
    }
4534
4535
    return wantarray ? @rules : \@rules;
4536
}
4537
4538
=head2 GetMarcPermissionsModules
4539
4540
    my $modules = GetMarcPermissionsModules()
4541
4542
Returns:
4543
4544
=over 4
4545
4546
=item C<$modules>
4547
4548
array (in list context, arrayref otherwise) of hashrefs from
4549
marc_permissions_modules table in order of specificity.
4550
4551
=back
4552
4553
=cut
4554
4555
sub GetMarcPermissionsModules {
4556
    my $dbh = C4::Context->dbh;
4557
    my @modules = ();
4558
4559
    my $query = '
4560
    SELECT *
4561
    FROM `marc_permissions_modules`
4562
    ORDER BY `specificity` DESC
4563
    ';
4564
    my $sth = $dbh->prepare($query);
4565
    $sth->execute();
4566
    while ( my $row = $sth->fetchrow_hashref ) {
4567
        push(@modules, $row);
4568
    }
4569
4570
    return wantarray ? @modules : \@modules;
4571
}
4572
4573
=head2 ModMarcPermissionsRule
4574
4575
    my $success = ModMarcPermissionsRule($id, $fields)
4576
4577
Modifies rule in the marc_permissions table.
4578
4579
=over 4
4580
4581
=item C<$id>
4582
4583
rule id to modify
4584
4585
=item C<$fields>
4586
4587
hashref defining the table fields
4588
4589
      * tagfield - required
4590
      * tagsubfield
4591
      * module - required
4592
      * filter - required
4593
      * on_existing - required
4594
      * on_new - required
4595
      * on_removed - required
4596
4597
=back
4598
4599
Returns:
4600
4601
=over 4
4602
4603
=item C<$success>
4604
4605
undef if an error occurs, otherwise true.
4606
4607
=back
4608
4609
=cut
4610
4611
sub ModMarcPermissionsRule {
4612
    my ($id, $f) = @_;
4613
    my $dbh = C4::Context->dbh;
4614
    my $query = '
4615
    UPDATE `marc_permissions`
4616
    SET
4617
      tagfield = ?,
4618
      tagsubfield = ?,
4619
      module = ?,
4620
      filter = ?,
4621
      on_existing = ?,
4622
      on_new = ?,
4623
      on_removed = ?
4624
    WHERE
4625
      id = ?
4626
    ';
4627
    my $sth = $dbh->prepare($query);
4628
    return $sth->execute (
4629
                      $f->{tagfield},
4630
                      $f->{tagsubfield},
4631
                      $f->{module},
4632
                      $f->{filter},
4633
                      $f->{on_existing},
4634
                      $f->{on_new},
4635
                      $f->{on_removed},
4636
                      $id
4637
                  );
4638
}
4639
4640
=head2 AddMarcPermissionsRule
4641
4642
    my $success = AddMarcPermissionsRule($fields)
4643
4644
Add rule to the marc_permissions table.
4645
4646
=over 4
4647
4648
=item C<$fields>
4649
4650
hashref defining the table fields
4651
4652
      tagfield - required
4653
      tagsubfield
4654
      module - required
4655
      filter - required
4656
      on_existing - required
4657
      on_new - required
4658
      on_removed - required
4659
4660
=back
4661
4662
Returns:
4663
4664
=over 4
4665
4666
=item C<$success>
4667
4668
undef if an error occurs, otherwise true.
4669
4670
=back
4671
4672
=cut
4673
4674
sub AddMarcPermissionsRule {
4675
    my $f = shift;
4676
    my $dbh = C4::Context->dbh;
4677
    my $query = '
4678
    INSERT INTO `marc_permissions`
4679
    (
4680
      tagfield,
4681
      tagsubfield,
4682
      module,
4683
      filter,
4684
      on_existing,
4685
      on_new,
4686
      on_removed
4687
    )
4688
    VALUES (?, ?, ?, ?, ?, ?, ?)
4689
    ';
4690
    my $sth = $dbh->prepare($query);
4691
    return $sth->execute (
4692
                      $f->{tagfield},
4693
                      $f->{tagsubfield},
4694
                      $f->{module},
4695
                      $f->{filter},
4696
                      $f->{on_existing},
4697
                      $f->{on_new},
4698
                      $f->{on_removed}
4699
                  );
4700
}
4701
4702
=head2 DelMarcPermissionsRule
4703
4704
    my $success = DelMarcPermissionsRule($id)
4705
4706
Deletes rule from the marc_permissions table.
4707
4708
=over 4
4709
4710
=item C<$id>
4711
4712
rule id to delete
4713
4714
=back
4715
4716
Returns:
4717
4718
=over 4
4719
4720
=item C<$success>
4721
4722
undef if an error occurs, otherwise true.
4723
4724
=back
4725
4726
=cut
4727
4728
sub DelMarcPermissionsRule {
4729
    my $id = shift;
4730
    my $dbh = C4::Context->dbh;
4731
    my $query = '
4732
    DELETE FROM `marc_permissions`
4733
    WHERE
4734
      id = ?
4735
    ';
4736
    my $sth = $dbh->prepare($query);
4737
    return $sth->execute($id);
4738
}
3789
1;
4739
1;
3790
4740
3791
4741
(-)a/C4/Installer/PerlDependencies.pm (+4 lines)
Lines 856-861 our $PERL_DEPS = { Link Here
856
        'usage'    => 'Interface third party systems',
856
        'usage'    => 'Interface third party systems',
857
        'required' => '0',
857
        'required' => '0',
858
        'min_ver'  => '0.07',
858
        'min_ver'  => '0.07',
859
    'String::Similarity' => {
860
        usage => 'cataloguing',
861
        required => 1,
862
        min_version => '1.04',
859
    },
863
    },
860
};
864
};
861
865
(-)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 (-2 / +1 lines)
Lines 417-423 RECORD: while ( ) { Link Here
417
			}
417
			}
418
					# create biblio, unless we already have it ( either match or isbn )
418
					# create biblio, unless we already have it ( either match or isbn )
419
            if ($biblionumber) {
419
            if ($biblionumber) {
420
                eval{$biblioitemnumber=GetBiblioData($biblionumber)->{biblioitemnumber};};
420
                eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber), { source => 'bulkmarcimport' }) };
421
                if ($update) {
421
                if ($update) {
422
                    eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
422
                    eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
423
                    if ($@) {
423
                    if ($@) {
Lines 805-808 from the migration_tools directory. Link Here
805
=back
805
=back
806
806
807
=cut
807
=cut
808
(-)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