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

(-)a/C4/Biblio.pm (-11 / +969 lines)
Lines 28-33 use MARC::File::USMARC; Link Here
28
use MARC::File::XML;
28
use MARC::File::XML;
29
use POSIX qw(strftime);
29
use POSIX qw(strftime);
30
use Module::Load::Conditional qw(can_load);
30
use Module::Load::Conditional qw(can_load);
31
use String::Similarity;
31
32
32
use C4::Koha;
33
use C4::Koha;
33
use C4::Log;    # logaction
34
use C4::Log;    # logaction
Lines 104-109 BEGIN { Link Here
104
      &CountItemsIssued
105
      &CountItemsIssued
105
      &CountBiblioInOrders
106
      &CountBiblioInOrders
106
      &GetSubscriptionsId
107
      &GetSubscriptionsId
108
109
      &GetMarcPermissionsRules
110
      &GetMarcPermissionsModules
111
      &ModMarcPermissionsRule
112
      &AddMarcPermissionsRule
113
      &DelMarcPermissionsRule
107
    );
114
    );
108
115
109
    # To modify something
116
    # To modify something
Lines 131-136 BEGIN { Link Here
131
    # they are useful in a few circumstances, so they are exported,
138
    # they are useful in a few circumstances, so they are exported,
132
    # but don't use them unless you are a core developer ;-)
139
    # but don't use them unless you are a core developer ;-)
133
    push @EXPORT, qw(
140
    push @EXPORT, qw(
141
      &ApplyMarcPermissions
134
      &ModBiblioMarc
142
      &ModBiblioMarc
135
    );
143
    );
136
144
Lines 268-274 sub AddBiblio { Link Here
268
276
269
=head2 ModBiblio
277
=head2 ModBiblio
270
278
271
  ModBiblio( $record,$biblionumber,$frameworkcode);
279
  ModBiblio($record, $biblionumber, $frameworkcode, $options);
272
280
273
Replace an existing bib record identified by C<$biblionumber>
281
Replace an existing bib record identified by C<$biblionumber>
274
with one supplied by the MARC::Record object C<$record>.  The embedded
282
with one supplied by the MARC::Record object C<$record>.  The embedded
Lines 289-295 Returns 1 on success 0 on failure Link Here
289
=cut
297
=cut
290
298
291
sub ModBiblio {
299
sub ModBiblio {
292
    my ( $record, $biblionumber, $frameworkcode ) = @_;
300
    my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
301
    $options //= {};
302
293
    if (!$record) {
303
    if (!$record) {
294
        carp 'No record passed to ModBiblio';
304
        carp 'No record passed to ModBiblio';
295
        return 0;
305
        return 0;
Lines 327-339 sub ModBiblio { Link Here
327
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
337
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
328
338
329
    # load the koha-table data object
339
    # load the koha-table data object
330
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
340
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode, undef, $biblionumber, { 'context' => $options->{'context'} } );
331
341
332
    # update MARC subfield that stores biblioitems.cn_sort
342
    # update MARC subfield that stores biblioitems.cn_sort
333
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
343
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
334
344
335
    # 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
336
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
346
    ModBiblioMarc( $record, $biblionumber, $frameworkcode, { 'context' => $options->{'context'} } );
337
347
338
    # modify the other koha tables
348
    # modify the other koha tables
339
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
349
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 2597-2606 sub TransformHtmlToMarc { Link Here
2597
2607
2598
=head2 TransformMarcToKoha
2608
=head2 TransformMarcToKoha
2599
2609
2600
  $result = TransformMarcToKoha( $record, $frameworkcode )
2610
  $result = TransformMarcToKoha($record, $frameworkcode, $biblionumber, $options)
2601
2611
2602
Extract data from a MARC bib record into a hashref representing
2612
Extract data from a MARC bib record into a hashref representing
2603
Koha biblio, biblioitems, and items fields. 
2613
Koha biblio, biblioitems, and items fields.
2604
2614
2605
If passed an undefined record will log the error and return an empty
2615
If passed an undefined record will log the error and return an empty
2606
hash_ref
2616
hash_ref
Lines 2608-2614 hash_ref Link Here
2608
=cut
2618
=cut
2609
2619
2610
sub TransformMarcToKoha {
2620
sub TransformMarcToKoha {
2611
    my ( $record, $frameworkcode, $limit_table ) = @_;
2621
    my ( $record, $frameworkcode, $limit_table, $biblionumber, $options ) = @_;
2612
2622
2613
    my $result = {};
2623
    my $result = {};
2614
    if (!defined $record) {
2624
    if (!defined $record) {
Lines 2629-2634 sub TransformMarcToKoha { Link Here
2629
        $tables{'biblioitems'} = 1;
2639
        $tables{'biblioitems'} = 1;
2630
    }
2640
    }
2631
2641
2642
    # apply permissions
2643
    if ( C4::Context->preference('MARCPermissions') && $biblionumber && defined $options && exists $options->{'context'} ) {
2644
        $record = ApplyMarcPermissions({
2645
                biblionumber => $biblionumber,
2646
                record => $record,
2647
                frameworkcode => $frameworkcode,
2648
                filter => $options->{'context'},
2649
                nolog => 1
2650
            });
2651
    }
2652
2632
    # traverse through record
2653
    # traverse through record
2633
  MARCFIELD: foreach my $field ( $record->fields() ) {
2654
  MARCFIELD: foreach my $field ( $record->fields() ) {
2634
        my $tag = $field->tag();
2655
        my $tag = $field->tag();
Lines 3412-3418 sub _koha_delete_biblio_metadata { Link Here
3412
3433
3413
=head2 ModBiblioMarc
3434
=head2 ModBiblioMarc
3414
3435
3415
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3436
  &ModBiblioMarc($newrec, $biblionumber, $frameworkcode, $options);
3416
3437
3417
Add MARC XML data for a biblio to koha
3438
Add MARC XML data for a biblio to koha
3418
3439
Lines 3423-3429 Function exported, but should NOT be used, unless you really know what you're do Link Here
3423
sub ModBiblioMarc {
3444
sub ModBiblioMarc {
3424
    # pass the MARC::Record to this function, and it will create the records in
3445
    # pass the MARC::Record to this function, and it will create the records in
3425
    # the marcxml field
3446
    # the marcxml field
3426
    my ( $record, $biblionumber, $frameworkcode ) = @_;
3447
    my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
3448
3427
    if ( !$record ) {
3449
    if ( !$record ) {
3428
        carp 'ModBiblioMarc passed an undefined record';
3450
        carp 'ModBiblioMarc passed an undefined record';
3429
        return;
3451
        return;
Lines 3436-3441 sub ModBiblioMarc { Link Here
3436
    if ( !$frameworkcode ) {
3458
    if ( !$frameworkcode ) {
3437
        $frameworkcode = "";
3459
        $frameworkcode = "";
3438
    }
3460
    }
3461
3462
    # apply permissions
3463
    if (
3464
        C4::Context->preference('MARCPermissions') &&
3465
        defined $options &&
3466
        exists $options->{'context'}
3467
    ) {
3468
        $record = ApplyMarcPermissions(
3469
            {
3470
                biblionumber => $biblionumber,
3471
                record => $record,
3472
                frameworkcode => $frameworkcode,
3473
                filter => $options->{'context'}
3474
            }
3475
        );
3476
    }
3477
3439
    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3478
    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3440
    $sth->execute( $frameworkcode, $biblionumber );
3479
    $sth->execute( $frameworkcode, $biblionumber );
3441
    $sth->finish;
3480
    $sth->finish;
Lines 3491-3497 sub ModBiblioMarc { Link Here
3491
3530
3492
    $count = &CountBiblioInOrders( $biblionumber);
3531
    $count = &CountBiblioInOrders( $biblionumber);
3493
3532
3494
This function return count of biblios in orders with $biblionumber 
3533
This function return count of biblios in orders with $biblionumber
3495
3534
3496
=cut
3535
=cut
3497
3536
Lines 3499-3505 sub CountBiblioInOrders { Link Here
3499
 my ($biblionumber) = @_;
3538
 my ($biblionumber) = @_;
3500
    my $dbh            = C4::Context->dbh;
3539
    my $dbh            = C4::Context->dbh;
3501
    my $query          = "SELECT count(*)
3540
    my $query          = "SELECT count(*)
3502
          FROM  aqorders 
3541
          FROM  aqorders
3503
          WHERE biblionumber=? AND (datecancellationprinted IS NULL OR datecancellationprinted='0000-00-00')";
3542
          WHERE biblionumber=? AND (datecancellationprinted IS NULL OR datecancellationprinted='0000-00-00')";
3504
    my $sth = $dbh->prepare($query);
3543
    my $sth = $dbh->prepare($query);
3505
    $sth->execute($biblionumber);
3544
    $sth->execute($biblionumber);
Lines 3756-3761 sub RemoveAllNsb { Link Here
3756
    return $record;
3795
    return $record;
3757
}
3796
}
3758
3797
3798
=head2 ApplyMarcPermissions
3799
3800
    my $record = ApplyMarcPermissions($arguments)
3801
3802
Applies marc permission rules to a record.
3803
3804
C<$arguments> is expected to be a hashref with below keys defined.
3805
3806
=over 4
3807
3808
=item C<biblionumber>
3809
biblionumber of old record
3810
3811
=item C<record>
3812
record that will modify old record
3813
3814
=item C<frameworkcode>
3815
only tags included in framework will be processed
3816
3817
=item C<filter>
3818
hashref containing at least one filter module from the marc_permissions_modules
3819
table in form {module => filter}. Three predefined filter modules exists:
3820
3821
    * source
3822
    * category
3823
    * borrower
3824
3825
=item C<log>
3826
optional reference to array that will be filled with rule evaluation log
3827
entries.
3828
3829
=item C<nolog>
3830
optional boolean which when true disables logging to action log.
3831
3832
=back
3833
3834
Returns:
3835
3836
=over 4
3837
3838
=item C<$record>
3839
3840
new MARC record based on C<record> with C<filter> applied. If no old
3841
record for C<biblionumber> can be found, C<record> is returned unchanged.
3842
Default action when no matching filter found is to leave old record unchanged.
3843
3844
=back
3845
3846
=cut
3847
3848
sub ApplyMarcPermissions {
3849
    my $arguments     = shift;
3850
    my $biblionumber  = $arguments->{biblionumber};
3851
    my $new_record    = $arguments->{record};
3852
    my $frameworkcode = $arguments->{frameworkcode} || '';
3853
3854
    if ( !$biblionumber ) {
3855
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3856
        return;
3857
    }
3858
    if ( !$new_record ) {
3859
        carp 'ApplyMarcPermissions called on undefined record';
3860
        return;
3861
    }
3862
    my $filter = $arguments->{filter}
3863
      or return $new_record;
3864
3865
    my $log   = $arguments->{log} || [];
3866
    my $nolog = $arguments->{nolog};
3867
3868
    my $dbh = C4::Context->dbh;
3869
3870
    my $is_regex = sub {
3871
        my ( $tag, $m ) = @_;
3872
        # tag is not exactly same as possible regex
3873
        $tag ne $m &&
3874
3875
        # wildcard
3876
        $m ne '*' &&
3877
3878
        # valid tagDataType
3879
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3880
        ([1-9A-z][\dA-z]{2})$/x &&
3881
3882
        # nor valid controltagDataType
3883
        $m !~ /00[1-9A-Za-z]{1}/ &&
3884
3885
        # so we try it as a regex
3886
        $tag =~ /^$m$/
3887
    };
3888
3889
    my $old_record = GetMarcBiblio($biblionumber);
3890
    if ( $old_record ) {
3891
        my $ret_record      = MARC::Record->new();
3892
        my $perm            = GetMarcPermissions() or return $new_record;
3893
        my $marc_struct     = GetMarcStructure(1, $frameworkcode);
3894
        my $similarity_pref = C4::Context->preference("MARCPermissionsCorrectionSimilarity");
3895
        $similarity_pref    = defined $similarity_pref &&
3896
                              $similarity_pref >= 0 &&
3897
                              $similarity_pref <= 100 ?
3898
                              $similarity_pref/100.0 : undef;
3899
3900
        # First came the leader ...
3901
        $ret_record->leader($old_record->leader());
3902
3903
        # ... and then came all the tags in the framework
3904
        for my $tag (sort keys %{$marc_struct}) {
3905
            my @old_fields = $old_record->field($tag);
3906
            my @new_fields = $new_record->field($tag);
3907
            next unless @old_fields or @new_fields;
3908
            my @perms = (
3909
                $perm->{'*'},
3910
                (   # regex tags
3911
                    map { $perm->{$_} }
3912
                      grep { $is_regex->( $tag, $_ ) } sort keys %{$perm}
3913
                ),
3914
                $perm->{$tag}
3915
            );
3916
3917
            # There can be more than one field for each tag, so we have to
3918
            # iterate all fields and distinguish which are new, removed or
3919
            # modified
3920
            my $max_fields = ($#old_fields, $#new_fields)
3921
                             [$#old_fields< $#new_fields];
3922
            for ( my $i_field = 0; $i_field <= $max_fields; $i_field++ ) {
3923
                my $old_field = $old_fields[$i_field];
3924
                my $new_field = $new_fields[$i_field];
3925
3926
                # Existing field
3927
                if ( $old_field and $new_field ) {
3928
                    # Control fields
3929
                    if ( $old_field->is_control_field() ) {
3930
                        # Existing control field
3931
                        if ( $old_field and $new_field ) {
3932
                            my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @perms);
3933
                            if ( defined $on_existing->{action} ) {
3934
                                push @{$log},
3935
                                  {
3936
                                    rule         => $on_existing->{rule},
3937
                                    event        => "existing",
3938
                                    action       => $on_existing->{action},
3939
                                    tag          => $tag,
3940
                                    subfieldcode => undef,
3941
                                    filter       => $filter
3942
                                  };
3943
                                if ( $on_existing->{action} eq 'skip' ) {
3944
                                    $ret_record->insert_fields_ordered($old_field);
3945
                                    next;
3946
                                } elsif ( $on_existing->{action} eq 'overwrite' ) {
3947
                                    $ret_record->insert_fields_ordered($new_field);
3948
                                    next;
3949
                                } elsif ( $on_existing->{action} eq 'add' ) {
3950
                                    $ret_record->insert_fields_ordered($old_field);
3951
                                    $ret_record->insert_fields_ordered($new_field);
3952
                                    next;
3953
                                } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
3954
                                    if ( $similarity_pref ) {
3955
                                        my $similarity = similarity $old_field->data(), $new_field->data();
3956
                                        if ( $similarity >= $similarity_pref ) {
3957
                                            $ret_record->insert_fields_ordered($new_field);
3958
                                        } else {
3959
                                            $ret_record->insert_fields_ordered($old_field);
3960
                                            $ret_record->insert_fields_ordered($new_field);
3961
                                        }
3962
                                    } else {
3963
                                        $ret_record->insert_fields_ordered($old_field);
3964
                                    }
3965
                                }
3966
                            } else { # default to skip
3967
                                $ret_record->insert_fields_ordered($old_field);
3968
                                next;
3969
                            }
3970
                        # New control field
3971
                        } elsif ( not $old_field and $new_field ) {
3972
                            my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
3973
                            if ( defined $on_new->{action} ) {
3974
                                push @{$log},
3975
                                  {
3976
                                    rule         => $on_new->{rule},
3977
                                    event        => "new",
3978
                                    action       => $on_new->{action},
3979
                                    tag          => $tag,
3980
                                    subfieldcode => undef,
3981
                                    filter       => $filter
3982
                                  };
3983
                                if ( $on_new->{action} eq 'skip' ) { # Redundant
3984
                                    next;
3985
                                } elsif ( $on_new->{action} eq 'add' ) {
3986
                                    $ret_record->insert_fields_ordered($new_field);
3987
                                    next;
3988
                                }
3989
                            }
3990
                        # Removed control field
3991
                        } elsif ( $old_field and not $new_field ) {
3992
                            my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
3993
                            if ( defined $on_removed->{action} ) {
3994
                                push @{$log},
3995
                                  {
3996
                                    rule         => $on_removed->{rule},
3997
                                    event        => "removed",
3998
                                    action       => $on_removed->{action},
3999
                                    tag          => $tag,
4000
                                    subfieldcode => undef
4001
                                  };
4002
                                if ( $on_removed->{action} eq 'skip' ) {
4003
                                    $ret_record->insert_fields_ordered($old_field);
4004
                                    next;
4005
                                } elsif ( $on_removed->{action} eq 'remove' ) {
4006
                                    next;
4007
                                }
4008
                            } else { # default to skip
4009
                                $ret_record->insert_fields_ordered($old_field);
4010
                                next;
4011
                            }
4012
                        }
4013
                    # Indicators and subfields
4014
                    } else {
4015
                        # Indicators
4016
                        my @old_ind = map { $old_field->indicator($_) } (1,2);
4017
                        my @new_ind = map { $new_field->indicator($_) } (1,2);
4018
                        my @ind = ('','');
4019
4020
                        for my $i ((0,1)) {
4021
                            my @ind_perms = (
4022
                                @perms,
4023
                                $perm->{$tag}->{subfields}->{ 'i' . ( $i + 1 ) }
4024
                            );
4025
4026
                            # Existing indicator
4027
                            if ( defined $old_ind[$i] and defined $new_ind[$i] ) {
4028
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @ind_perms);
4029
                                if ( defined $on_existing->{action} ) {
4030
                                    push @{$log},
4031
                                      {
4032
                                        rule         => $on_existing->{rule},
4033
                                        event        => "existing",
4034
                                        action       => $on_existing->{action},
4035
                                        tag          => $tag,
4036
                                        subfieldcode => 'i' . ( $i + 1 ),
4037
                                        filter       => $filter
4038
                                      };
4039
                                    if ( $on_existing->{action} eq 'skip' ) {
4040
                                        $ind[$i] = $old_ind[$i];
4041
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4042
                                        $ind[$i] = $new_ind[$i];
4043
                                    }
4044
                                } else { # default to skip
4045
                                    $ind[$i] = $old_ind[$i];
4046
                                }
4047
                            # New indicator
4048
                            } elsif ( not defined $old_ind[$i] and defined $new_ind[$i] ) {
4049
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @ind_perms);
4050
                                if ( defined $on_new->{action} ) {
4051
                                    push @{$log},
4052
                                      {
4053
                                        rule         => $on_new->{rule},
4054
                                        event        => "new",
4055
                                        action       => $on_new->{action},
4056
                                        tag          => $tag,
4057
                                        subfieldcode => 'i' . ( $i + 1 ),
4058
                                        filter       => $filter
4059
                                      };
4060
                                    if ( $on_new->{action} eq 'skip' ) {
4061
                                        $ind[$i] = $old_ind[$i];
4062
                                    } elsif ( $on_new->{action} eq 'add' ) {
4063
                                        $ind[$i] = $new_ind[$i];
4064
                                    }
4065
                                }
4066
                            # Removed indicator
4067
                            } elsif ( defined $old_ind[$i] and not defined $new_ind[$i] ) {
4068
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @ind_perms);
4069
                                if ( defined $on_removed->{action} ) {
4070
                                    push @{$log},
4071
                                      {
4072
                                        rule         => $on_removed->{rule},
4073
                                        event        => "removed",
4074
                                        action       => $on_removed->{action},
4075
                                        tag          => $tag,
4076
                                        subfieldcode => 'i' . ( $i + 1 ),
4077
                                        filter       => $filter
4078
                                      };
4079
                                    if ( $on_removed->{action} eq 'skip' ) {
4080
                                        $ind[$i] = $old_ind[$i];
4081
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4082
                                        $ind[$i] = '';
4083
                                    }
4084
                                } else { # default to skip
4085
                                    $ind[$i] = $old_ind[$i];
4086
                                }
4087
                            }
4088
                        }
4089
4090
                        # Subfields
4091
                        my @subfields = ();
4092
                        my %proccessed_subcodes = ();
4093
4094
                        # Try to preserve subfield order by first processing
4095
                        # subfields from the old record, and then subfields from
4096
                        # the framework structure unless include in old record.
4097
                        my @sorted_subfields = (
4098
                            map { shift @{$_} } $old_field->subfields(),
4099
                            () = sort keys %{ $marc_struct->{$tag} }
4100
                        );
4101
                        for my $subcode ( @sorted_subfields ) {
4102
                            next if $proccessed_subcodes{$subcode};
4103
                            $proccessed_subcodes{$subcode} = 1;
4104
                            next unless ref $marc_struct->{$tag}{$subcode} eq 'HASH';
4105
                            my @old_subfields = $old_field->subfield($subcode);
4106
                            my @new_subfields = $new_field->subfield($subcode);
4107
                            next unless @old_subfields or @new_subfields;
4108
4109
                            my @subfield_perms = (
4110
                                @perms,
4111
                                $perm->{'*'}->{subfields}->{'*'},
4112
                                $perm->{$tag}->{subfields}->{'*'},
4113
                                (    # tag->regex
4114
                                    map { $perm->{$tag}->{subfields}->{$_} }
4115
                                      grep {
4116
                                        # subcode is not exactly same as
4117
                                        # possible regex
4118
                                        $subcode ne $_ &&
4119
4120
                                        # wildcard, not checking for valid
4121
                                        # subfieldcodeDataType since it
4122
                                        # contains too many regex characters
4123
                                        $_ !~ /^(\*)$/ &&
4124
4125
                                        # so we try it as a regex
4126
                                        $subcode =~ /$_/
4127
                                      } sort
4128
                                      keys %{ $perm->{$tag}->{subfields} }
4129
                                ),
4130
                                (    # regex->*
4131
                                    map { $perm->{$_}->{subfields}->{'*'} }
4132
                                      grep { $is_regex->( $tag, $_ ) }
4133
                                      sort keys %{$perm}
4134
                                ),
4135
                                $perm->{'*'}->{subfields}->{$subcode},
4136
                                (    # regex->subcode
4137
                                    map { $perm->{$_}->{subfields}->{$subcode} }
4138
                                      grep { $is_regex->( $tag, $_ ) }
4139
                                      sort keys %{$perm}
4140
                                ),
4141
                                $perm->{$tag}->{subfields}->{$subcode}
4142
                            );
4143
4144
                            # Existing subfield
4145
                            if ( @old_subfields and @new_subfields ) {
4146
                                my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @subfield_perms);
4147
                                if ( defined $on_existing->{action} ) {
4148
                                    push @{$log},
4149
                                      {
4150
                                        rule         => $on_existing->{rule},
4151
                                        event        => "existing",
4152
                                        action       => $on_existing->{action},
4153
                                        tag          => $tag,
4154
                                        subfieldcode => $subcode,
4155
                                        filter       => $filter
4156
                                      };
4157
                                    if ( $on_existing->{action} eq 'skip' ) {
4158
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4159
                                        next;
4160
                                    } elsif ( $on_existing->{action} eq 'overwrite' ) {
4161
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4162
                                        next;
4163
                                    } elsif ( $on_existing->{action} eq 'add' ) {
4164
                                        push(@subfields, map {($subcode,$_)} @old_subfields);
4165
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4166
                                        next;
4167
                                    } elsif ( $on_existing->{action} eq 'add_or_correct' ) {
4168
                                        if ( $similarity_pref ) {
4169
                                            my @corrected_vals = ();
4170
4171
                                            # correct all old subfields
4172
                                            for ( my $i_new = 0; $i_new <= $#new_subfields; $i_new++ ) {
4173
                                                my $new_val = $new_subfields[$i_new];
4174
                                                for ( my $i_old = 0; $i_old <= $#old_subfields; $i_old++ ) {
4175
                                                    my $old_val = $old_subfields[$i_old];
4176
                                                    next if not defined $old_val or not defined $new_val;
4177
                                                    my $similarity = similarity ($old_val, $new_val);
4178
                                                    if ( $similarity >= $similarity_pref ) {
4179
                                                        $corrected_vals[$i_old] = $new_val;
4180
                                                        $new_subfields[$i_new] = undef;
4181
                                                        $old_subfields[$i_old] = undef;
4182
                                                        last;
4183
                                                    }
4184
                                                }
4185
                                            }
4186
4187
                                            # insert all unchanged old subfields
4188
                                            map {
4189
                                                $corrected_vals[$_] = $old_subfields[$_]
4190
                                                    if defined $old_subfields[$_]
4191
                                                } 0 .. $#old_subfields;
4192
4193
4194
                                            # append all remaning new subfields
4195
                                            map {push @corrected_vals, $_ if defined} @new_subfields;
4196
4197
                                            push(@subfields, map {($subcode,$_)} @corrected_vals);
4198
                                        } else {
4199
                                            push(@subfields, map {($subcode,$_)} @old_subfields);
4200
                                        }
4201
                                    }
4202
                                } else { # default to skip
4203
                                    push(@subfields, map {($subcode,$_)} @old_subfields);
4204
                                    next;
4205
                                }
4206
                            # New subfield
4207
                            } elsif ( not @old_subfields and @new_subfields ) {
4208
                                my $on_new = GetMarcPermissionsAction('on_new', $filter, @subfield_perms);
4209
                                if ( defined $on_new->{action} ) {
4210
                                    push @{$log},
4211
                                      {
4212
                                        rule         => $on_new->{rule},
4213
                                        event        => "new",
4214
                                        action       => $on_new->{action},
4215
                                        tag          => $tag,
4216
                                        subfieldcode => $subcode,
4217
                                        filter       => $filter
4218
                                      };
4219
                                    if ( $on_new->{action} eq 'skip' ) {
4220
                                        next;
4221
                                    } elsif ( $on_new->{action} eq 'add' ) {
4222
                                        push(@subfields, map {($subcode,$_)} @new_subfields);
4223
                                        next;
4224
                                    }
4225
                                }
4226
                            # Removed subfield
4227
                            } elsif ( @old_subfields and not @new_subfields ) {
4228
                                my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @subfield_perms);
4229
                                if ( defined $on_removed->{action} ) {
4230
                                    push @{$log},
4231
                                      {
4232
                                        rule         => $on_removed->{rule},
4233
                                        event        => "removed",
4234
                                        action       => $on_removed->{action},
4235
                                        tag          => $tag,
4236
                                        subfieldcode => $subcode,
4237
                                        filter       => $filter
4238
                                      };
4239
                                    if ( $on_removed->{action} eq 'skip' ) {
4240
                                        push(@subfields, ($subcode => @old_subfields));
4241
                                        next;
4242
                                    } elsif ( $on_removed->{action} eq 'remove' ) {
4243
                                        next;
4244
                                    }
4245
                                } else { # default to skip
4246
                                    push(@subfields, ($subcode => @old_subfields));
4247
                                    next;
4248
                                }
4249
                            }
4250
                        } # / for each subfield
4251
                        $ret_record->insert_grouped_field(MARC::Field->new($tag, $ind[0], $ind[1], @subfields));
4252
                    }
4253
                # New field
4254
                } elsif ( not $old_field and $new_field ) {
4255
                    my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms);
4256
4257
                    if ( defined $on_new->{action} ) {
4258
                        push @{$log},
4259
                          {
4260
                            rule         => $on_new->{rule},
4261
                            event        => "new",
4262
                            action       => $on_new->{action},
4263
                            tag          => $tag,
4264
                            subfieldcode => undef,
4265
                            filter       => $filter
4266
                          };
4267
                        if ( $on_new->{action} eq 'skip' ) {
4268
                            next;
4269
                        } elsif ( $on_new->{action} eq 'add' ) {
4270
                            $ret_record->insert_fields_ordered($new_field);
4271
                            next;
4272
                        }
4273
                    }
4274
                # Removed field
4275
                } elsif ( $old_field and not $new_field ) {
4276
                    my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms);
4277
                    if ( defined $on_removed->{action} ) {
4278
                        push @{$log},
4279
                          {
4280
                            rule         => $on_removed->{rule},
4281
                            event        => "removed",
4282
                            action       => $on_removed->{action},
4283
                            tag          => $tag,
4284
                            subfieldcode => undef,
4285
                            filter       => $filter
4286
                          };
4287
                        if ( $on_removed->{action} eq 'skip' ) {
4288
                            $ret_record->insert_grouped_field($old_field);
4289
                            next;
4290
                        } elsif ( $on_removed->{action} eq 'remove' ) {
4291
                            next;
4292
                        }
4293
                    } else { # default to skip
4294
                        $ret_record->insert_grouped_field($old_field);
4295
                        next;
4296
                    }
4297
                }
4298
            } # / for each field
4299
        } # / for each tag
4300
        if ( !$nolog && C4::Context->preference("MARCPermissionsLog") ) {
4301
            my $log_str = '';
4302
            my $n       = $#{$log};
4303
            for my $l ( @{$log} ) {
4304
                $log_str .= "{\n";
4305
                my $kn = keys %{$l};
4306
                for my $k ( sort keys %{$l} ) {
4307
                    if ( $k eq 'filter' ) {
4308
                        $log_str .= "  filter => {\n";
4309
                        my $fkn = keys %{ $l->{$k} };
4310
                        for my $fk ( sort keys %{ $l->{$k} } ) {
4311
                            $log_str .= '    '
4312
                              . $fk . ' => '
4313
                              . $l->{$k}->{$fk}
4314
                              . ( --$fkn ? ',' : '' ) . "\n";
4315
                        }
4316
                        $log_str .= "  }" . ( --$kn ? ',' : '' ) . "\n";
4317
                    }
4318
                    else {
4319
                        $log_str .= '  '
4320
                          . $k . ' => '
4321
                          . $l->{$k}
4322
                          . ( --$kn ? ',' : '' ) . "\n"
4323
                          if defined $l->{$k};
4324
                    }
4325
                }
4326
                $log_str .= '}' . ( $n-- ? ",\n" : '' );
4327
            }
4328
            logaction( "CATALOGUING", "MODIFY", $biblionumber, $log_str );
4329
        }
4330
        return $ret_record;
4331
    }
4332
    return $new_record;
4333
}
4334
4335
4336
=head2 GetMarcPermissions
4337
4338
    my $marc_permissions = GetMarcPermissions()
4339
4340
Loads MARC field permissions from the marc_permissions table.
4341
4342
Returns:
4343
4344
=over 4
4345
4346
=item C<$marc_permissions>
4347
4348
hashref with permissions structure for use with GetMarcPermissionsAction.
4349
4350
=back
4351
4352
=cut
4353
4354
sub GetMarcPermissions {
4355
    my $dbh = C4::Context->dbh;
4356
    my $rule_count = 0;
4357
    my %perms = ();
4358
4359
    my $query = '
4360
    SELECT `marc_permissions`.*,
4361
           `marc_permissions_modules`.`name`,
4362
           `marc_permissions_modules`.`description`,
4363
           `marc_permissions_modules`.`specificity`
4364
    FROM `marc_permissions`
4365
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4366
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4367
    ';
4368
    my $sth = $dbh->prepare($query);
4369
    $sth->execute();
4370
    while ( my $row = $sth->fetchrow_hashref ) {
4371
        $rule_count++;
4372
        if ( defined $row->{tagsubfield} and $row->{tagsubfield} ) {
4373
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4374
              ->{on_existing}->{ $row->{name} }->{ $row->{filter} } =
4375
              { action => $row->{on_existing}, rule => $row->{'id'} };
4376
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4377
              ->{on_new}->{ $row->{name} }->{ $row->{filter} } =
4378
              { action => $row->{on_new}, rule => $row->{'id'} };
4379
            $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} }
4380
              ->{on_removed}->{ $row->{name} }->{ $row->{filter} } =
4381
              { action => $row->{on_removed}, rule => $row->{'id'} };
4382
        }
4383
        else {
4384
            $perms{ $row->{tagfield} }->{on_existing}->{ $row->{name} }
4385
              ->{ $row->{filter} } =
4386
              { action => $row->{on_existing}, rule => $row->{'id'} };
4387
            $perms{ $row->{tagfield} }->{on_new}->{ $row->{name} }
4388
              ->{ $row->{filter} } =
4389
              { action => $row->{on_new}, rule => $row->{'id'} };
4390
            $perms{ $row->{tagfield} }->{on_removed}->{ $row->{name} }
4391
              ->{ $row->{filter} } =
4392
              { action => $row->{on_removed}, rule => $row->{'id'} };
4393
        }
4394
    }
4395
4396
    return unless $rule_count;
4397
    return \%perms;
4398
}
4399
4400
=head2 GetMarcPermissionsAction
4401
4402
    my $action = GetMarcPermissionsAction($event, $filter, @permissions)
4403
4404
Gets action based on C<$event>, C<$filter> and C<@permissions>.
4405
4406
=over 4
4407
4408
=item C<$event>
4409
4410
which event: 'on_existing', 'on_new' or 'on_removed'
4411
4412
=item C<$filter>
4413
4414
hashref containing at least one filter module from the marc_permissions_modules
4415
table in form {module => filter}. Three predefined filter modules exists:
4416
4417
    * source
4418
    * category
4419
    * borrower
4420
4421
=item C<@permissions>
4422
4423
list of permission structures in order of specificity from least to most
4424
specific.
4425
4426
=back
4427
4428
Returns:
4429
4430
=over 4
4431
4432
=item C<$action>
4433
4434
hashref defining matching action.
4435
4436
=back
4437
4438
=cut
4439
4440
sub GetMarcPermissionsAction {
4441
    my $what = shift or return;
4442
    my $filter = shift or return;
4443
    my @perms = @_;
4444
    my $modules = GetMarcPermissionsModules();
4445
    my $action = undef;
4446
4447
    for my $perm (@perms) {
4448
        next if not defined $perm;
4449
        next if not defined $perm->{$what};
4450
        my $tmp_action = undef;
4451
        for my $module ( @{$modules} ) {
4452
            my $action_candidate = defined $perm->{$what}->{ $module->{'name'} } ? $perm->{$what}->{ $module->{'name'} } : undef;
4453
            if ($action_candidate) {
4454
                if (
4455
                    defined $filter->{ $module->{'name'} } and
4456
                    defined $action_candidate->{ $filter->{ $module->{'name'} } }->{action}
4457
                ) {
4458
                    $tmp_action = $perm->{$what}->{ $module->{'name'} }->{ $filter->{ $module->{'name'} } };
4459
                    last;
4460
                }
4461
                elsif (defined $action_candidate->{'*'}->{action} and !$tmp_action) {
4462
                    $tmp_action = $perm->{$what}->{ $module->{'name'} }->{'*'};
4463
                }
4464
            }
4465
        }
4466
        $action = $tmp_action if defined $tmp_action;
4467
    }
4468
    return $action;
4469
}
4470
4471
=head2 GetMarcPermissionsRules
4472
4473
    my $rules = GetMarcPermissionsRules()
4474
4475
Returns:
4476
4477
=over 4
4478
4479
=item C<$rules>
4480
4481
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
4482
table in order of module specificity and rule id.
4483
4484
=back
4485
4486
=cut
4487
4488
sub GetMarcPermissionsRules {
4489
    my $dbh = C4::Context->dbh;
4490
    my @rules = ();
4491
4492
    my $query = '
4493
    SELECT `marc_permissions`.`id`,
4494
           `marc_permissions`.`tagfield`,
4495
           `marc_permissions`.`tagsubfield`,
4496
           `marc_permissions`.`filter`,
4497
           `marc_permissions`.`on_existing`,
4498
           `marc_permissions`.`on_new`,
4499
           `marc_permissions`.`on_removed`,
4500
           `marc_permissions_modules`.`name` as  `module`,
4501
           `marc_permissions_modules`.`description`,
4502
           `marc_permissions_modules`.`specificity`
4503
    FROM `marc_permissions`
4504
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
4505
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
4506
    ';
4507
    my $sth = $dbh->prepare($query);
4508
    $sth->execute();
4509
    while ( my $row = $sth->fetchrow_hashref ) {
4510
        push(@rules, $row);
4511
    }
4512
4513
    return wantarray ? @rules : \@rules;
4514
}
4515
4516
=head2 GetMarcPermissionsModules
4517
4518
    my $modules = GetMarcPermissionsModules()
4519
4520
Returns:
4521
4522
=over 4
4523
4524
=item C<$modules>
4525
4526
array (in list context, arrayref otherwise) of hashrefs from
4527
marc_permissions_modules table in order of specificity.
4528
4529
=back
4530
4531
=cut
4532
4533
sub GetMarcPermissionsModules {
4534
    my $dbh = C4::Context->dbh;
4535
    my @modules = ();
4536
4537
    my $query = '
4538
    SELECT *
4539
    FROM `marc_permissions_modules`
4540
    ORDER BY `specificity` DESC
4541
    ';
4542
    my $sth = $dbh->prepare($query);
4543
    $sth->execute();
4544
    while ( my $row = $sth->fetchrow_hashref ) {
4545
        push(@modules, $row);
4546
    }
4547
4548
    return wantarray ? @modules : \@modules;
4549
}
4550
4551
=head2 ModMarcPermissionsRule
4552
4553
    my $success = ModMarcPermissionsRule($id, $fields)
4554
4555
Modifies rule in the marc_permissions table.
4556
4557
=over 4
4558
4559
=item C<$id>
4560
4561
rule id to modify
4562
4563
=item C<$fields>
4564
4565
hashref defining the table fields
4566
4567
      * tagfield - required
4568
      * tagsubfield
4569
      * module - required
4570
      * filter - required
4571
      * on_existing - required
4572
      * on_new - required
4573
      * on_removed - required
4574
4575
=back
4576
4577
Returns:
4578
4579
=over 4
4580
4581
=item C<$success>
4582
4583
undef if an error occurs, otherwise true.
4584
4585
=back
4586
4587
=cut
4588
4589
sub ModMarcPermissionsRule {
4590
    my ($id, $f) = @_;
4591
    my $dbh = C4::Context->dbh;
4592
    my $query = '
4593
    UPDATE `marc_permissions`
4594
    SET
4595
      tagfield = ?,
4596
      tagsubfield = ?,
4597
      module = ?,
4598
      filter = ?,
4599
      on_existing = ?,
4600
      on_new = ?,
4601
      on_removed = ?
4602
    WHERE
4603
      id = ?
4604
    ';
4605
    my $sth = $dbh->prepare($query);
4606
    return $sth->execute (
4607
                      $f->{tagfield},
4608
                      $f->{tagsubfield},
4609
                      $f->{module},
4610
                      $f->{filter},
4611
                      $f->{on_existing},
4612
                      $f->{on_new},
4613
                      $f->{on_removed},
4614
                      $id
4615
                  );
4616
}
4617
4618
=head2 AddMarcPermissionsRule
4619
4620
    my $success = AddMarcPermissionsRule($fields)
4621
4622
Add rule to the marc_permissions table.
4623
4624
=over 4
4625
4626
=item C<$fields>
4627
4628
hashref defining the table fields
4629
4630
      tagfield - required
4631
      tagsubfield
4632
      module - required
4633
      filter - required
4634
      on_existing - required
4635
      on_new - required
4636
      on_removed - required
4637
4638
=back
4639
4640
Returns:
4641
4642
=over 4
4643
4644
=item C<$success>
4645
4646
undef if an error occurs, otherwise true.
4647
4648
=back
4649
4650
=cut
4651
4652
sub AddMarcPermissionsRule {
4653
    my $f = shift;
4654
    my $dbh = C4::Context->dbh;
4655
    my $query = '
4656
    INSERT INTO `marc_permissions`
4657
    (
4658
      tagfield,
4659
      tagsubfield,
4660
      module,
4661
      filter,
4662
      on_existing,
4663
      on_new,
4664
      on_removed
4665
    )
4666
    VALUES (?, ?, ?, ?, ?, ?, ?)
4667
    ';
4668
    my $sth = $dbh->prepare($query);
4669
    return $sth->execute (
4670
                      $f->{tagfield},
4671
                      $f->{tagsubfield},
4672
                      $f->{module},
4673
                      $f->{filter},
4674
                      $f->{on_existing},
4675
                      $f->{on_new},
4676
                      $f->{on_removed}
4677
                  );
4678
}
4679
4680
=head2 DelMarcPermissionsRule
4681
4682
    my $success = DelMarcPermissionsRule($id)
4683
4684
Deletes rule from the marc_permissions table.
4685
4686
=over 4
4687
4688
=item C<$id>
4689
4690
rule id to delete
4691
4692
=back
4693
4694
Returns:
4695
4696
=over 4
4697
4698
=item C<$success>
4699
4700
undef if an error occurs, otherwise true.
4701
4702
=back
4703
4704
=cut
4705
4706
sub DelMarcPermissionsRule {
4707
    my $id = shift;
4708
    my $dbh = C4::Context->dbh;
4709
    my $query = '
4710
    DELETE FROM `marc_permissions`
4711
    WHERE
4712
      id = ?
4713
    ';
4714
    my $sth = $dbh->prepare($query);
4715
    return $sth->execute($id);
4716
}
3759
1;
4717
1;
3760
4718
3761
4719
(-)a/C4/ImportBatch.pm (-1 / +1 lines)
Lines 674-680 sub BatchCommitRecords { Link Here
674
                }
674
                }
675
                $oldxml = $old_marc->as_xml($marc_type);
675
                $oldxml = $old_marc->as_xml($marc_type);
676
676
677
                ModBiblio($marc_record, $recordid, $oldbiblio->{'frameworkcode'});
677
                ModBiblio($marc_record, $recordid, $oldbiblio->{'frameworkcode'}, {context => {source => 'batchimport'}});
678
                $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
678
                $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
679
679
680
                if ($item_result eq 'create_new' || $item_result eq 'replace') {
680
                if ($item_result eq 'create_new' || $item_result eq 'replace') {
(-)a/C4/Installer/PerlDependencies.pm (+5 lines)
Lines 862-867 our $PERL_DEPS = { Link Here
862
        'required' => '0',
862
        'required' => '0',
863
        'min_ver'  => '0.07',
863
        'min_ver'  => '0.07',
864
    },
864
    },
865
    'String::Similarity' => {
866
        usage => 'cataloguing',
867
        required => 1,
868
        min_version => '1.04',
869
    },
865
};
870
};
866
871
867
1;
872
1;
(-)a/C4/Items.pm (+2 lines)
Lines 400-405 sub AddItemBatchFromMarc { Link Here
400
        logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
400
        logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
401
401
402
        my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
402
        my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
403
404
        # @FIXME: why modify clone record here since not even saving below?
403
        $item_field->replace_with($new_item_marc->field($itemtag));
405
        $item_field->replace_with($new_item_marc->field($itemtag));
404
    }
406
    }
405
407
(-)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 (-2 / +11 lines)
Lines 836-844 if ( $op eq "addbiblio" ) { Link Here
836
        my $oldbibitemnum;
836
        my $oldbibitemnum;
837
        if (C4::Context->preference("BiblioAddsAuthorities")){
837
        if (C4::Context->preference("BiblioAddsAuthorities")){
838
            BiblioAutoLink( $record, $frameworkcode );
838
            BiblioAutoLink( $record, $frameworkcode );
839
        } 
839
        }
840
        if ( $is_a_modif ) {
840
        if ( $is_a_modif ) {
841
            ModBiblio( $record, $biblionumber, $frameworkcode );
841
            my ($member) = C4::Members::GetMember('borrowernumber' => $loggedinuser);
842
            ModBiblio( $record, $biblionumber, $frameworkcode, {
843
                    context => {
844
                        source => $z3950 ? 'z39.50' : 'intranet',
845
                        category => $member->{'category_type'},
846
                        borrower => $loggedinuser
847
                    }
848
                }
849
            );
842
        }
850
        }
843
        else {
851
        else {
844
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
852
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
Lines 931-936 elsif ( $op eq "delete" ) { Link Here
931
    $template->param(
939
    $template->param(
932
        biblionumberdata => $biblionumber,
940
        biblionumberdata => $biblionumber,
933
        op               => $op,
941
        op               => $op,
942
        z3950            => $z3950
934
    );
943
    );
935
    if ( $op eq "duplicate" ) {
944
    if ( $op eq "duplicate" ) {
936
        $biblionumber = "";
945
        $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 72-77 Logging: Link Here
72
                  on: Log
72
                  on: Log
73
                  off: "Don't log"
73
                  off: "Don't log"
74
            - when reports are added, deleted or changed.
74
            - when reports are added, deleted or changed.
75
        -
76
            - pref: MARCPermissionsLog
77
              choices:
78
                  on: Log
79
                  off: "Don't log"
80
            - MARC permissions rule evaluations when applying rules for MARC field modifications.
75
    Debugging:
81
    Debugging:
76
        -
82
        -
77
            - pref: DumpTemplateVarsIntranet
83
            - 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), {context => {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, {context => {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