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

(-)a/C4/Biblio.pm (-8 / +609 lines)
Lines 102-107 BEGIN { Link Here
102
102
103
      &CountItemsIssued
103
      &CountItemsIssued
104
      &CountBiblioInOrders
104
      &CountBiblioInOrders
105
106
      &GetMarcPermissionsRules
107
      &GetMarcPermissionsModules
108
      &ModMarcPermissionsRule
109
      &AddMarcPermissionsRule
110
      &DelMarcPermissionsRule
105
    );
111
    );
106
112
107
    # To modify something
113
    # To modify something
Lines 129-134 BEGIN { Link Here
129
    # they are useful in a few circumstances, so they are exported,
135
    # they are useful in a few circumstances, so they are exported,
130
    # but don't use them unless you are a core developer ;-)
136
    # but don't use them unless you are a core developer ;-)
131
    push @EXPORT, qw(
137
    push @EXPORT, qw(
138
      &ApplyMarcPermissions
132
      &ModBiblioMarc
139
      &ModBiblioMarc
133
    );
140
    );
134
141
Lines 266-272 sub AddBiblio { Link Here
266
273
267
=head2 ModBiblio
274
=head2 ModBiblio
268
275
269
  ModBiblio( $record,$biblionumber,$frameworkcode);
276
  ModBiblio($record, $biblionumber, $frameworkcode, $options);
270
277
271
Replace an existing bib record identified by C<$biblionumber>
278
Replace an existing bib record identified by C<$biblionumber>
272
with one supplied by the MARC::Record object C<$record>.  The embedded
279
with one supplied by the MARC::Record object C<$record>.  The embedded
Lines 287-293 Returns 1 on success 0 on failure Link Here
287
=cut
294
=cut
288
295
289
sub ModBiblio {
296
sub ModBiblio {
290
    my ( $record, $biblionumber, $frameworkcode ) = @_;
297
    my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
298
    $options //= {};
299
291
    if (!$record) {
300
    if (!$record) {
292
        carp 'No record passed to ModBiblio';
301
        carp 'No record passed to ModBiblio';
293
        return 0;
302
        return 0;
Lines 315-320 sub ModBiblio { Link Here
315
324
316
    _strip_item_fields($record, $frameworkcode);
325
    _strip_item_fields($record, $frameworkcode);
317
326
327
    # apply permissions
328
    if (C4::Context->preference('MARCPermissions') && $biblionumber && defined $options && exists $options->{'context'}) {
329
        $record = ApplyMarcPermissions({
330
                biblionumber => $biblionumber,
331
                record => $record,
332
                filter => $options->{'context'},
333
            }
334
        );
335
    }
336
318
    # update biblionumber and biblioitemnumber in MARC
337
    # update biblionumber and biblioitemnumber in MARC
319
    # FIXME - this is assuming a 1 to 1 relationship between
338
    # FIXME - this is assuming a 1 to 1 relationship between
320
    # biblios and biblioitems
339
    # biblios and biblioitems
Lines 325-337 sub ModBiblio { Link Here
325
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
344
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
326
345
327
    # load the koha-table data object
346
    # load the koha-table data object
328
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
347
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode, undef, $biblionumber);
329
348
330
    # update MARC subfield that stores biblioitems.cn_sort
349
    # update MARC subfield that stores biblioitems.cn_sort
331
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
350
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
332
351
333
    # update the MARC record (that now contains biblio and items) with the new record data
352
    # update the MARC record (that now contains biblio and items) with the new record data
334
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
353
    ModBiblioMarc( $record, $biblionumber, $frameworkcode );
335
354
336
    # modify the other koha tables
355
    # modify the other koha tables
337
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
356
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 2573-2579 sub TransformHtmlToMarc { Link Here
2573
2592
2574
=head2 TransformMarcToKoha
2593
=head2 TransformMarcToKoha
2575
2594
2576
    $result = TransformMarcToKoha( $record, undef, $limit )
2595
    $result = TransformMarcToKoha($record, undef, $biblionumber, $option)
2577
2596
2578
Extract data from a MARC bib record into a hashref representing
2597
Extract data from a MARC bib record into a hashref representing
2579
Koha biblio, biblioitems, and items fields.
2598
Koha biblio, biblioitems, and items fields.
Lines 2584-2590 hash_ref. Link Here
2584
=cut
2603
=cut
2585
2604
2586
sub TransformMarcToKoha {
2605
sub TransformMarcToKoha {
2587
    my ( $record, $frameworkcode, $limit_table ) = @_;
2606
    my ( $record, $frameworkcode, $limit_table, $biblionumber ) = @_;
2588
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2607
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2589
    $limit_table //= q{};
2608
    $limit_table //= q{};
2590
2609
Lines 3271-3277 sub _koha_delete_biblio_metadata { Link Here
3271
3290
3272
=head2 ModBiblioMarc
3291
=head2 ModBiblioMarc
3273
3292
3274
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3293
  &ModBiblioMarc($newrec, $biblionumber, $frameworkcode, $options);
3275
3294
3276
Add MARC XML data for a biblio to koha
3295
Add MARC XML data for a biblio to koha
3277
3296
Lines 3283-3288 sub ModBiblioMarc { Link Here
3283
    # pass the MARC::Record to this function, and it will create the records in
3302
    # pass the MARC::Record to this function, and it will create the records in
3284
    # the marcxml field
3303
    # the marcxml field
3285
    my ( $record, $biblionumber, $frameworkcode ) = @_;
3304
    my ( $record, $biblionumber, $frameworkcode ) = @_;
3305
3286
    if ( !$record ) {
3306
    if ( !$record ) {
3287
        carp 'ModBiblioMarc passed an undefined record';
3307
        carp 'ModBiblioMarc passed an undefined record';
3288
        return;
3308
        return;
Lines 3295-3300 sub ModBiblioMarc { Link Here
3295
    if ( !$frameworkcode ) {
3315
    if ( !$frameworkcode ) {
3296
        $frameworkcode = "";
3316
        $frameworkcode = "";
3297
    }
3317
    }
3318
3298
    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3319
    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3299
    $sth->execute( $frameworkcode, $biblionumber );
3320
    $sth->execute( $frameworkcode, $biblionumber );
3300
    $sth->finish;
3321
    $sth->finish;
Lines 3596-3603 sub RemoveAllNsb { Link Here
3596
    return $record;
3617
    return $record;
3597
}
3618
}
3598
3619
3599
1;
3620
=head2 ApplyMarcPermissions
3621
3622
    my $record = ApplyMarcPermissions($arguments)
3623
3624
Applies marc permission rules to a record.
3625
3626
C<$arguments> is expected to be a hashref with below keys defined.
3627
3628
=over 4
3629
3630
=item C<biblionumber>
3631
biblionumber of old record
3632
3633
=item C<record>
3634
record that will modify old record
3635
3636
=item C<frameworkcode>
3637
only tags included in framework will be processed
3638
3639
=item C<filter>
3640
hashref containing at least one filter module from the marc_permissions_modules
3641
table in form {module => filter}. Three predefined filter modules exists:
3642
3643
    * source
3644
    * category
3645
    * borrower
3646
3647
=item C<log>
3648
optional reference to array that will be filled with rule evaluation log
3649
entries.
3650
3651
=item C<nolog>
3652
optional boolean which when true disables logging to action log.
3653
3654
=back
3655
3656
Returns:
3657
3658
=over 4
3659
3660
=item C<$record>
3661
3662
new MARC record based on C<record> with C<filter> applied. If no old
3663
record for C<biblionumber> can be found, C<record> is returned unchanged.
3664
Default action when no matching filter found is to leave old record unchanged.
3665
3666
=back
3667
3668
=cut
3669
3670
sub ApplyMarcPermissions {
3671
    my ($arguments) = @_;
3672
    my $biblionumber = $arguments->{biblionumber};
3673
    my $incoming_record = $arguments->{record};
3674
3675
    if ( !$biblionumber ) {
3676
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3677
        return;
3678
    }
3679
    if ( !$incoming_record ) {
3680
        carp 'ApplyMarcPermissions called on undefined record';
3681
        return;
3682
    }
3683
    my $old_record = GetMarcBiblio({ biblionumber => $biblionumber });
3684
3685
    my $merge_rules = undef;
3686
    if ($old_record && $arguments->{filter} && ($merge_rules = GetMarcPermissions($arguments->{filter}))) {
3687
        return MergeRecords($old_record, $incoming_record, $merge_rules);
3688
    }
3689
    return $incoming_record;
3690
}
3691
3692
sub MergeRecords {
3693
    my ($old_record, $incoming_record, $merge_rules) = @_;
3694
    my $is_matching_regex = sub {
3695
        my ( $tag, $m ) = @_;
3696
3697
        # tag is not exactly same as possible regex
3698
        $tag ne $m &&
3699
3700
        # wildcard
3701
        $m ne '*' &&
3702
3703
        # valid tagDataType
3704
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3705
        ([1-9A-z][\dA-z]{2})$/x &&
3706
3707
        # nor valid controltagDataType
3708
        $m !~ /00[1-9A-Za-z]{1}/ &&
3709
3710
        # so we try it as a regex
3711
        $tag =~ /^$m$/
3712
    };
3713
3714
    my $fields_by_tag = sub {
3715
        my ($record) = @_;
3716
        my $fields = {};
3717
        foreach my $field ($record->fields()) {
3718
            $fields->{$field->tag()} //= [];
3719
            push @{$fields->{$field->tag()}}, $field;
3720
        }
3721
        return $fields;
3722
    };
3723
3724
    my $hash_field_data = sub {
3725
        my ($field) = @_;
3726
        my $indicators = join("\x1E", map { $field->indicator($_) } (1, 2));
3727
        return $indicators . "\x1E" . join("\x1E", sort map { join "\x1E", @{$_} } $field->subfields());
3728
    };
3729
3730
    my $diff_by_key = sub {
3731
        my ($a, $b) = @_;
3732
        my @removed;
3733
        my @intersecting;
3734
        my @added;
3735
        my %keys_index = map { $_ => undef } (keys %{$a}, keys %{$b});
3736
        foreach my $key (keys %keys_index) {
3737
            if ($a->{$key} && $b->{$key}) {
3738
                push @intersecting, $a->{$key};
3739
            }
3740
            elsif ($a->{$key}) {
3741
                push @removed, $a->{$key};
3742
            }
3743
            else {
3744
                push @added, $b->{$key};
3745
            }
3746
        }
3747
        return (\@removed, \@intersecting, \@added);
3748
    };
3749
3750
    my $get_matching_field_rule = sub {
3751
        my ($tag) = @_;
3752
        my $matched_rule = undef;
3753
        # Exact match takes precedence
3754
        if (exists $merge_rules->{$tag}) {
3755
            $matched_rule = $merge_rules->{$tag};
3756
        }
3757
        else {
3758
            # TODO: sorty by module/weight rule id or something, grab first matching via listutils thingy
3759
            my @matching_rules = map { $merge_rules->{$_} } grep { $is_matching_regex->($tag, $_) } sort keys %{$merge_rules};
3760
            # TODO: fix
3761
            if (@matching_rules) {
3762
                $matched_rule = pop @matching_rules;
3763
            }
3764
            elsif($merge_rules->{'*'}) {
3765
                $matched_rule = $merge_rules->{'*'};
3766
            }
3767
        }
3768
        return $matched_rule;
3769
    };
3770
3771
    my $merged_record = MARC::Record->new();
3772
    my @merged_record_fields;
3773
3774
    # Leader is always overwritten, or kept???
3775
    $merged_record->leader($incoming_record->leader());
3776
3777
    my $current_fields = $fields_by_tag->($old_record);
3778
    my $incoming_fields = $fields_by_tag->($incoming_record);
3779
3780
    # First we get all new incoming control fields
3781
    my @new_field_tags = grep { !(exists $current_fields->{$_}) } keys %{$incoming_fields};
3782
3783
    foreach my $tag (@new_field_tags) {
3784
        my $rule = $get_matching_field_rule->($tag) // {
3785
            on_new => {'action' => 'skip', 'rule' => 0}
3786
        };
3787
        if (
3788
            $rule->{on_new}->{action} eq 'add' ||
3789
            $rule->{on_new}->{action} eq 'overwrite' # ???
3790
        ) { # Or could just be write/protect?
3791
            # Hmm, only one control field possible??
3792
            push @merged_record_fields, @{$incoming_fields->{$tag}};
3793
        }
3794
    }
3795
3796
    # Then we get all control fields no longer present in incoming fields
3797
    # (removed)
3798
    my @deleted_field_tags = grep { !(exists $incoming_fields->{$_}) } keys %{$current_fields};
3799
    foreach my $tag (@deleted_field_tags) {
3800
        my $rule = $get_matching_field_rule->($tag) // {
3801
            on_deleted => {'action' => 'skip', 'rule' => 0}
3802
        };
3803
        if ($rule->{on_deleted}->{action} eq 'skip') {
3804
            push @merged_record_fields, @{$current_fields->{$tag}};
3805
        }
3806
    }
3807
3808
    # Then we get the intersection of control fields, present both in
3809
    # current and incoming record (possibly to be overwritten)
3810
    my @common_field_tags = grep { exists $incoming_fields->{$_} } keys %{$current_fields};
3811
    foreach my $tag (@common_field_tags) {
3812
        # Is control field
3813
        my $rule = $get_matching_field_rule->($tag) // {
3814
            on_removed => {'action' => 'skip', 'rule' => 0},
3815
            on_appended => {'action' => 'skip', 'rule' => 0}
3816
        };
3817
        if ($tag < 10) {
3818
            # on_existing = on_match
3819
            if ($rule->{on_appended}->{action} eq 'skip') { # TODO: replace with "protect", "keep"
3820
                push @merged_record_fields, @{$current_fields->{$tag}};
3821
            }
3822
            elsif ($rule->{on_appended}->{action} eq 'append') {
3823
                push @merged_record_fields, @{$incoming_fields->{$tag}};
3824
            }
3825
            if (
3826
                $rule->{on_appended}->{action} eq 'append' &&
3827
                $rule->{on_removed}->{action} eq 'skip'
3828
            ) {
3829
                #TODO: This is an invalid combination for control fields, warn!!
3830
                # Or should perform client/server side validation to prevent this choice
3831
            }
3832
        }
3833
        else {
3834
            # Compute intersection and diff using field data
3835
            my %current_fields_by_data = map { $hash_field_data->($_) => $_ } @{$current_fields->{$tag}};
3836
            my %incoming_fields_by_data = map { $hash_field_data->($_) => $_ } @{$incoming_fields->{$tag}};
3837
            my ($current_fields_only, $common_fields, $incoming_fields_only) = $diff_by_key->(\%current_fields_by_data, \%incoming_fields_by_data);
3838
3839
            # First add common fields (intersection)
3840
            # Unchanged
3841
            if (@{$common_fields}) {
3842
                push @merged_record_fields, @{$common_fields};
3843
            }
3844
            # Removed
3845
            if (@{$current_fields_only}) {
3846
                if ($rule->{on_removed}->{action} eq 'skip') {
3847
                    push @merged_record_fields, @{$current_fields_only};
3848
                }
3849
            }
3850
            # Appended
3851
            if (@{$incoming_fields_only}) {
3852
                if ($rule->{on_appended}->{action} eq 'append') {
3853
                    push @merged_record_fields, @{$incoming_fields_only};
3854
                }
3855
            }
3856
        }
3857
    }
3858
    if ($#merged_record_fields != 0) {
3859
        $merged_record->insert_fields_ordered(@merged_record_fields);
3860
    }
3861
    return $merged_record;
3862
}
3863
3864
=head2 GetMarcPermissions
3865
3866
    my $marc_permissions = GetMarcPermissions()
3867
3868
Loads MARC field permissions from the marc_permissions table.
3869
3870
Returns:
3871
3872
=over 4
3873
3874
=item C<$marc_permissions>
3875
3876
hashref with permissions structure for use with GetMarcPermissionsAction.
3877
3878
=back
3879
3880
=cut
3881
3882
sub GetMarcPermissions {
3883
    my ($filter) = @_;
3884
    my $dbh = C4::Context->dbh;
3885
    my $rule_count = 0;
3886
    my $modules = GetMarcPermissionsModules();
3887
    # We only care about modules included in the context/filter
3888
    # TODO: Perhaps make sure source => '*' is default?
3889
    my @filter_modules = grep { exists $filter->{$_->{name}} } @{$modules};
3890
3891
    my $cache = Koha::Caches->get_instance();
3892
    my $permissions = $cache->get_from_cache('marc_permissions', { unsafe => 1 });
3893
3894
    if (!$permissions) {
3895
        my $query = '
3896
            SELECT `marc_permissions`.*,
3897
                `marc_permissions_modules`.`name`,
3898
                `marc_permissions_modules`.`description`,
3899
                `marc_permissions_modules`.`specificity`
3900
                FROM `marc_permissions`
3901
                LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
3902
                ORDER BY `marc_permissions_modules`.`specificity`, `id`
3903
        ';
3904
        my $sth = $dbh->prepare($query);
3905
        $sth->execute();
3906
        while (my $perm = $sth->fetchrow_hashref) {
3907
            my $target = ($permissions->{$perm->{name}}->{$perm->{filter}}->{$perm->{tagfield}} //= {});
3908
            foreach my $event (GetMarcPermissionEvents()) {
3909
                $target->{$event} = { action => $perm->{$event}, rule => $perm->{'id'} };
3910
            }
3911
        }
3912
        $cache->set_in_cache('marc_permissions', $permissions);
3913
    }
3914
3915
    my $filtered_permissions = undef;
3916
    foreach my $module (@filter_modules) {
3917
        if (
3918
            exists $permissions->{$module->{name}} &&
3919
            exists $permissions->{$module->{name}}->{$filter->{$module->{name}}}
3920
        ) {
3921
            # TODO: Support multiple overlapping filters/context??
3922
            $filtered_permissions = $permissions->{$module->{name}}->{$filter->{$module->{name}}};
3923
            last;
3924
        }
3925
    }
3926
    if (!$filtered_permissions) {
3927
        # No perms matching specific context conditions found, try wildcard value for each active context
3928
        foreach my $module (@filter_modules) {
3929
            if (exists $permissions->{$module->{name}}->{'*'}) {
3930
                $filtered_permissions = $permissions->{$module->{name}}->{'*'};
3931
                last;
3932
            }
3933
        }
3934
    }
3935
    return $filtered_permissions;
3936
}
3937
3938
# TODO: Use this in GetMarcPermissions
3939
=head2 GetMarcPermissionsRules
3940
3941
    my $rules = GetMarcPermissionsRules()
3942
3943
Returns:
3944
3945
=over 4
3946
3947
=item C<$rules>
3948
3949
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
3950
table in order of module specificity and rule id.
3951
3952
=back
3953
3954
=cut
3955
3956
sub GetMarcPermissionsRules {
3957
    my $dbh = C4::Context->dbh;
3958
    my @rules = ();
3959
3960
    my $query = '
3961
    SELECT `marc_permissions`.`id`,
3962
           `marc_permissions`.`tagfield`,
3963
           `marc_permissions`.`filter`,
3964
           `marc_permissions`.`on_new`,
3965
           `marc_permissions`.`on_appended`,
3966
           `marc_permissions`.`on_removed`,
3967
           `marc_permissions`.`on_deleted`,
3968
           `marc_permissions_modules`.`name` as  `module`,
3969
           `marc_permissions_modules`.`description`,
3970
           `marc_permissions_modules`.`specificity`
3971
    FROM `marc_permissions`
3972
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
3973
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
3974
    ';
3975
    my $sth = $dbh->prepare($query);
3976
    $sth->execute();
3977
    while ( my $row = $sth->fetchrow_hashref ) {
3978
        push(@rules, $row);
3979
    }
3980
3981
    return wantarray ? @rules : \@rules;
3982
}
3983
3984
=head2 GetMarcPermissionsModules
3985
3986
    my $modules = GetMarcPermissionsModules()
3987
3988
Returns:
3989
3990
=over 4
3991
3992
=item C<$modules>
3993
3994
array (in list context, arrayref otherwise) of hashrefs from
3995
marc_permissions_modules table in order of specificity.
3996
3997
=back
3998
3999
=cut
4000
4001
sub GetMarcPermissionsModules {
4002
    my $dbh = C4::Context->dbh;
4003
    my @modules = ();
4004
4005
    my $query = '
4006
    SELECT *
4007
    FROM `marc_permissions_modules`
4008
    ORDER BY `specificity` DESC
4009
    ';
4010
    my $sth = $dbh->prepare($query);
4011
    $sth->execute();
4012
    while ( my $row = $sth->fetchrow_hashref ) {
4013
        push(@modules, $row);
4014
    }
4015
4016
    return wantarray ? @modules : \@modules;
4017
}
4018
4019
sub GetMarcPermissionEvents {
4020
    return ('on_new', 'on_appended', 'on_removed', 'on_deleted');
4021
}
4022
4023
=head2 ModMarcPermissionsRule
4024
4025
    my $success = ModMarcPermissionsRule($id, $fields)
4026
4027
Modifies rule in the marc_permissions table.
4028
4029
=over 4
4030
4031
=item C<$id>
4032
4033
rule id to modify
4034
4035
=item C<$fields>
4036
4037
hashref defining the table fields
4038
4039
      * tagfield - required
4040
      * module - required
4041
      * filter - required
4042
      * on_new - required
4043
      * on_appended - required
4044
      * on_removed - required
4045
      * on_deleted - required
4046
4047
=back
4048
4049
Returns:
4050
4051
=over 4
4052
4053
=item C<$success>
4054
4055
undef if an error occurs, otherwise true.
4056
4057
=back
3600
4058
4059
=cut
4060
4061
sub ModMarcPermissionsRule {
4062
    my ($id, $f) = @_;
4063
    my $dbh = C4::Context->dbh;
4064
4065
    my $query = '
4066
    UPDATE `marc_permissions`
4067
    SET
4068
      tagfield = ?,
4069
      module = ?,
4070
      filter = ?,
4071
      on_new = ?,
4072
      on_appended = ?,
4073
      on_removed = ?,
4074
      on_deleted = ?
4075
    WHERE
4076
      id = ?
4077
    ';
4078
    my $sth = $dbh->prepare($query);
4079
    my $result = $sth->execute (
4080
        $f->{tagfield},
4081
        $f->{module},
4082
        $f->{filter},
4083
        $f->{on_new},
4084
        $f->{on_appended},
4085
        $f->{on_removed},
4086
        $f->{on_deleted},
4087
        $id
4088
    );
4089
    ClearMarcPermissionsRulesCache();
4090
    return $result;
4091
}
4092
4093
sub ClearMarcPermissionsRulesCache {
4094
    my $cache = Koha::Caches->get_instance();
4095
    $cache->clear_from_cache('marc_permissions');
4096
}
4097
4098
=head2 AddMarcPermissionsRule
4099
4100
    my $success = AddMarcPermissionsRule($fields)
4101
4102
Add rule to the marc_permissions table.
4103
4104
=over 4
4105
4106
=item C<$fields>
4107
4108
hashref defining the table fields
4109
4110
      tagfield - required
4111
      module - required
4112
      filter - required
4113
      on_new - required
4114
      on_appended - required
4115
      on_removed - required
4116
      on_deleted - required
4117
4118
=back
4119
4120
Returns:
4121
4122
=over 4
4123
4124
=item C<$success>
4125
4126
undef if an error occurs, otherwise true.
4127
4128
=back
4129
4130
=cut
4131
4132
sub AddMarcPermissionsRule {
4133
    my $f = shift;
4134
    my $dbh = C4::Context->dbh;
4135
    my $query = '
4136
    INSERT INTO `marc_permissions`
4137
    (
4138
      tagfield,
4139
      module,
4140
      filter,
4141
      on_new,
4142
      on_appended,
4143
      on_removed,
4144
      on_deleted
4145
    )
4146
    VALUES (?, ?, ?, ?, ?, ?, ?)
4147
    ';
4148
    my $sth = $dbh->prepare($query);
4149
    my $result = $sth->execute (
4150
        $f->{tagfield},
4151
        $f->{module},
4152
        $f->{filter},
4153
        $f->{on_new},
4154
        $f->{on_appended},
4155
        $f->{on_removed},
4156
        $f->{on_deleted}
4157
    );
4158
    ClearMarcPermissionsRulesCache();
4159
    return $result;
4160
}
4161
4162
=head2 DelMarcPermissionsRule
4163
4164
    my $success = DelMarcPermissionsRule($id)
4165
4166
Deletes rule from the marc_permissions table.
4167
4168
=over 4
4169
4170
=item C<$id>
4171
4172
rule id to delete
4173
4174
=back
4175
4176
Returns:
4177
4178
=over 4
4179
4180
=item C<$success>
4181
4182
undef if an error occurs, otherwise true.
4183
4184
=back
4185
4186
=cut
4187
4188
sub DelMarcPermissionsRule {
4189
    my $id = shift;
4190
    my $dbh = C4::Context->dbh;
4191
    my $query = '
4192
    DELETE FROM `marc_permissions`
4193
    WHERE
4194
      id = ?
4195
    ';
4196
    my $sth = $dbh->prepare($query);
4197
    my $result = $sth->execute($id);
4198
    ClearMarcPermissionsRulesCache();
4199
    return $result;
4200
}
4201
1;
3601
4202
3602
__END__
4203
__END__
3603
4204
(-)a/C4/ImportBatch.pm (-1 / +1 lines)
Lines 666-672 sub BatchCommitRecords { Link Here
666
                }
666
                }
667
                $oldxml = $old_marc->as_xml($marc_type);
667
                $oldxml = $old_marc->as_xml($marc_type);
668
668
669
                ModBiblio($marc_record, $recordid, $oldbiblio->frameworkcode);
669
                ModBiblio($marc_record, $recordid, $oldbiblio->frameworkcode, {context => {source => 'batchimport'}});
670
                $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
670
                $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
671
671
672
                if ($item_result eq 'create_new' || $item_result eq 'replace') {
672
                if ($item_result eq 'create_new' || $item_result eq 'replace') {
(-)a/C4/Installer/PerlDependencies.pm (+5 lines)
Lines 867-872 our $PERL_DEPS = { Link Here
867
        'required' => '0',
867
        'required' => '0',
868
        'min_ver'  => '0.17',
868
        'min_ver'  => '0.17',
869
    },
869
    },
870
    'String::Similarity' => {
871
        usage => 'cataloguing',
872
        required => 1,
873
        min_version => '1.04',
874
    },
870
};
875
};
871
876
872
1;
877
1;
(-)a/admin/marc-permissions.pl (+115 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 $rule_from_cgi = sub {
44
    my ($cgi) = @_;
45
    my %rule = map { $_ => scalar $cgi->param($_) } (
46
        'tagfield',
47
        'module',
48
        'filter',
49
        'on_new',
50
        'on_appended',
51
        'on_removed',
52
        'on_deleted'
53
    );
54
    return \%rule;
55
};
56
57
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
58
    {
59
        template_name   => "admin/marc-permissions.tt",
60
        query           => $input,
61
        type            => "intranet",
62
        authnotrequired => 0,
63
        flagsrequired   => { parameters => 'parameters_remaining_permissions' },
64
        debug           => 1,
65
    }
66
);
67
68
my %cookies = parse CGI::Cookie($cookie);
69
our $sessionID = $cookies{'CGISESSID'}->value;
70
71
my $rules;
72
if ( $op eq "remove" ) {
73
    $template->{VARS}->{removeConfirm} = 1;
74
    my @removeIDs = $input->multi_param('batchremove');
75
    push( @removeIDs, scalar $input->param('id') ) if $input->param('id');
76
77
    $rules = GetMarcPermissionsRules();
78
    for my $removeID (@removeIDs) {
79
        map { $_->{'remove'} = 1 if $_->{'id'} == $removeID } @{$rules};
80
    }
81
}
82
elsif ( $op eq "doremove" ) {
83
    my @removeIDs = $input->multi_param('batchremove');
84
    push( @removeIDs, scalar $input->param('id') ) if $input->param('id');
85
    for my $removeID (@removeIDs) {
86
        DelMarcPermissionsRule($removeID);
87
    }
88
    $rules = GetMarcPermissionsRules();
89
}
90
elsif ( $op eq "edit" ) {
91
    $template->{VARS}->{edit} = 1;
92
    my $id = $input->param('id');
93
    $rules = GetMarcPermissionsRules();
94
    map { $_->{'edit'} = 1 if $_->{'id'} == $id } @{$rules};
95
96
}
97
elsif ( $op eq "doedit" ) {
98
    my $id = $input->param('id');
99
    my $rule = $rule_from_cgi->($input);
100
    ModMarcPermissionsRule($id, $rule);
101
    $rules = GetMarcPermissionsRules();
102
}
103
elsif ( $op eq "add" ) {
104
    my $rule = $rule_from_cgi->($input);
105
    AddMarcPermissionsRule($rule);
106
    $rules = GetMarcPermissionsRules();
107
108
}
109
else {
110
    $rules = GetMarcPermissionsRules();
111
}
112
my $modules = GetMarcPermissionsModules();
113
$template->param( rules => $rules, modules => $modules );
114
115
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/cataloguing/addbiblio.pl (-1 / +11 lines)
Lines 41-46 use Koha::ItemTypes; Link Here
41
use Koha::Libraries;
41
use Koha::Libraries;
42
42
43
use Koha::BiblioFrameworks;
43
use Koha::BiblioFrameworks;
44
use Koha::Patrons;
44
45
45
use Date::Calc qw(Today);
46
use Date::Calc qw(Today);
46
use MARC::File::USMARC;
47
use MARC::File::USMARC;
Lines 846-852 if ( $op eq "addbiblio" ) { Link Here
846
            BiblioAutoLink( $record, $frameworkcode );
847
            BiblioAutoLink( $record, $frameworkcode );
847
        } 
848
        } 
848
        if ( $is_a_modif ) {
849
        if ( $is_a_modif ) {
849
            ModBiblio( $record, $biblionumber, $frameworkcode );
850
            my $member = Koha::Patrons->find($loggedinuser);
851
            ModBiblio( $record, $biblionumber, $frameworkcode, {
852
                    context => {
853
                        source => $z3950 ? 'z39.50' : 'intranet',
854
                        category => $member->{'category_type'},
855
                        borrower => $loggedinuser
856
                    }
857
                }
858
            );
850
        }
859
        }
851
        else {
860
        else {
852
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
861
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
Lines 939-944 elsif ( $op eq "delete" ) { Link Here
939
    $template->param(
948
    $template->param(
940
        biblionumberdata => $biblionumber,
949
        biblionumberdata => $biblionumber,
941
        op               => $op,
950
        op               => $op,
951
        z3950            => $z3950
942
    );
952
    );
943
    if ( $op eq "duplicate" ) {
953
    if ( $op eq "duplicate" ) {
944
        $biblionumber = "";
954
        $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 IF NOT EXISTS `marc_permissions` (
21
    `id` int(11) NOT NULL auto_increment,
22
    `tagfield` varchar(255) NOT NULL, -- can be regexe, so need > 3 chars
23
    `module` int(11) NOT NULL,
24
    `filter` varchar(255) NOT NULL,
25
    `on_new` ENUM('skip', 'add') DEFAULT NULL,
26
    `on_appended` ENUM('skip', 'append') DEFAULT NULL,
27
    `on_removed` ENUM('skip', 'remove') DEFAULT NULL,
28
    `on_deleted` ENUM('skip', 'delete') 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 36-41 Link Here
36
    <li><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></li>
36
    <li><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></li>
37
    <li><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></li>
37
    <li><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></li>
38
    <li><a href="/cgi-bin/koha/admin/items_search_fields.pl">Item search fields</a></li>
38
    <li><a href="/cgi-bin/koha/admin/items_search_fields.pl">Item search fields</a></li>
39
    <li><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></li>
39
</ul>
40
</ul>
40
41
41
<h5>Acquisition parameters</h5>
42
<h5>Acquisition parameters</h5>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+2 lines)
Lines 90-95 Link Here
90
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt>
90
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt>
91
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd>
91
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd>
92
                    [% END %]
92
                    [% END %]
93
                    <dt><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></dt>
94
                    <dd>Managed MARC field permissions</dd>
93
                </dl>
95
                </dl>
94
96
95
                <h3>Acquisition parameters</h3>
97
                <h3>Acquisition parameters</h3>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-permissions.tt (+320 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 batch remove unless one or more checkboxes are checked */
68
    $('input[name="batchremove"]').change(function() {
69
        if($('input[name="batchremove"]:checked').length > 0) {
70
            $('#btn_batchremove').removeAttr('disabled');
71
        } else {
72
            $('#btn_batchremove').attr('disabled', 'disabled');
73
        }
74
    });
75
76
    /* check validity of regexes in field */
77
    $('input[name="tagfield"]').change(function() {
78
        $(this).removeClass('required');
79
        $(this).attr('title', '');
80
        if($(this).val() != '*') {
81
            try {
82
                new RegExp($(this).val())
83
            } catch ( e ) {
84
                $(this).addClass('required');
85
                $(this).attr('title', 'Invalid regular expression: ' + e.message);
86
            }
87
        }
88
    });
89
90
    $.fn.dataTable.ext.order['dom-input'] = function (settings, col) {
91
        return this.api().column(col, { order: 'index' }).nodes()
92
            .map(function (td, i) {
93
                if($('input', td).val() != undefined) {
94
                    return $('input', td).val();
95
                } else if($('select', td).val() != undefined) {
96
                    return $('option[selected="selected"]', td).val();
97
                } else {
98
                    return $(td).html();
99
                }
100
            });
101
    }
102
103
    $('#marc-permissions').dataTable($.extend(true, {}, dataTablesDefaults, {
104
        "aoColumns": [
105
            {"bSearchable": false, "bSortable": false},
106
            {"sSortDataType": "dom-input"},
107
            {"sSortDataType": "dom-input"},
108
            {"bSearchable": false, "sSortDataType": "dom-input"},
109
            {"bSearchable": false, "sSortDataType": "dom-input"},
110
            {"bSearchable": false, "sSortDataType": "dom-input"},
111
            {"bSearchable": false, "sSortDataType": "dom-input"},
112
            {"bSearchable": false, "sSortDataType": "dom-input"},
113
            {"bSearchable": false, "sSortDataType": "dom-input"},
114
            {"bSearchable": false, "bSortable": false},
115
            {"bSearchable": false, "bSortable": false}
116
        ],
117
        "sPaginationType": "four_button"
118
    }));
119
120
});
121
//]]>
122
</script>
123
</head>
124
<body id="admin_marc-permissions" class="admin">
125
[% INCLUDE 'header.inc' %]
126
[% INCLUDE 'cat-search.inc' %]
127
128
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a>
129
 &rsaquo; MARC field permissions
130
</div>
131
132
<div id="doc3" class="yui-t2">
133
   <div id="bd">
134
    <div id="yui-main">
135
    <div class="yui-b">
136
137
<h1>Manage MARC field permissions</h1>
138
139
[% UNLESS Koha.Preference( 'MARCPermissions' ) %]
140
    <div class="dialog message">
141
        The <b>MARCPermissions</b> preference is not set, don't forget to enable it for rules to take effect.
142
    </div>
143
[% END %]
144
[% IF removeConfirm %]
145
<div class="dialog alert">
146
<h3>Remove rule?</h3>
147
<p>Are you sure you want to remove the selected rule(s)?</p>
148
149
<form action="[% script_name %]" method="GET">
150
    <input type="submit" value="No, do not remove" class="deny"/>
151
</form>
152
<input type="button" value="Yes, remove" class="approve" id="doremove" />
153
</div>
154
[% END %]
155
156
<form action="[% script_name %]" method="POST" id="marc-permissions-form">
157
<table id="marc-permissions">
158
    <thead><tr>
159
        <th>Rule</th>
160
        <th>Tag</th>
161
        <th>Module</th>
162
        <th>Filter</th>
163
        <th>On New</th>
164
        <th>On Appended</th>
165
        <th>On Removed</th>
166
        <th>On Deleted</th>
167
        <th>Actions</th>
168
        <th>&nbsp;</th>
169
    </tr></thead>
170
    [% UNLESS edit %]
171
    <tfoot>
172
        <tr>
173
            <th>&nbsp;</th>
174
            <th><input type="text" size="5" name="tagfield"/></th>
175
            <th>
176
                <select name="module">
177
                    [% FOREACH module IN modules %]
178
                    <option value="[% module.id %]">[% module.name %]</option>
179
                    [% END %]
180
                </select>
181
            </th>
182
            <th><input type="text" size="5" name="filter"/></th>
183
            <th>
184
                <select name="on_new">
185
                [% FOR on_new IN ['skip', 'add'] %]
186
                    <option value="[% on_new %]">[% on_new %]</option>
187
                [% END %]
188
                </select>
189
            </th>
190
            <th>
191
                <select name="on_appended">
192
                [% FOR on_appended IN ['skip', 'append'] %]
193
                    <option value="[% on_appended %]">[% on_appended %]</option>
194
                [% END %]
195
                </select>
196
            </th>
197
            <th>
198
                <select name="on_removed">
199
                [% FOR on_removed IN ['skip', 'remove'] %]
200
                    <option value="[% on_removed %]">[% on_removed %]</option>
201
                [% END %]
202
                </select>
203
            </th>
204
            <th>
205
                <select name="on_deleted">
206
                [% FOR on_deleted IN ['skip', 'delete'] %]
207
                    <option value="[% on_deleted %]">[% on_deleted %]</option>
208
                [% END %]
209
                </select>
210
            </th>
211
            <th><button class="btn btn-small" title="Add" id="add"><i class="fa fa-plus"></i> Add rule</button></th>
212
            <th><button id="btn_batchremove" disabled="disabled" class="btn btn-small" title="Batch remove"><i class="fa fa-trash"></i> Delete selected</button></th>
213
        </tr>
214
    </tfoot>
215
    [% END %]
216
    <tbody>
217
        [% FOREACH rule IN rules %]
218
            <tr id="[% rule.id %]">
219
            [% IF rule.edit %]
220
                <td>[% rule.id %]</td>
221
                <td><input type="text" size="3" name="tagfield" value="[% rule.tagfield %]"/></td>
222
                <td>
223
                    <select name="module">
224
                        [% FOREACH module IN modules %]
225
                            [% IF module.name == rule.module %]
226
                                <option value="[% module.id %]" selected="selected">[% module.name %]</option>
227
                            [% ELSE %]
228
                                <option value="[% module.id %]">[% module.name %]</option>
229
                            [% END %]
230
                        [% END %]
231
                    </select>
232
                </td>
233
                <td><input type="text" size="5" name="filter" value="[% rule.filter %]"/></td>
234
                <td>
235
                    <select name="on_new">
236
                        [% FOR on_new IN ['skip', 'add'] %]
237
                            [% IF on_new == rule.on_new %]
238
                                <option value="[% on_new %]" selected="selected">[% on_new %]</option>
239
                            [% ELSE %]
240
                                <option value="[% on_new %]">[% on_new %]</option>
241
                            [% END %]
242
                        [% END %]
243
                    </select>
244
                </td>
245
                <td>
246
                    <select name="on_appended">
247
                        [% FOR on_appended IN ['skip', 'append'] %]
248
                            [% IF on_appended == rule.on_append %]
249
                                <option value="[% on_appended %]" selected="selected">[% on_appended %]</option>
250
                            [% ELSE %]
251
                                <option value="[% on_appended %]">[% on_appended %]</option>
252
                            [% END %]
253
                        [% END %]
254
                    </select>
255
                </td>
256
                <td>
257
                    <select name="on_removed">
258
                        [% FOR on_removed IN ['skip', 'remove'] %]
259
                            [% IF on_removed == rule.on_removed %]
260
                                <option value="[% on_removed %]" selected="selected">[% on_removed %]</option>
261
                            [% ELSE %]
262
                                <option value="[% on_removed %]">[% on_removed %]</option>
263
                            [% END %]
264
                        [% END %]
265
                    </select>
266
                </td>
267
                <td>
268
                    <select name="on_deleted">
269
                        [% FOR on_deleted IN ['skip', 'delete'] %]
270
                            [% IF on_deleted == rule.on_deleted %]
271
                                <option value="[% on_deleted %]" selected="selected">[% on_deleted %]</option>
272
                            [% ELSE %]
273
                                <option value="[% on_deleted %]">[% on_deleted %]</option>
274
                            [% END %]
275
                        [% END %]
276
                    </select>
277
                </td>
278
                <td class="actions">
279
                    <button class="btn btn-mini" title="Save" id="doedit" value="[% rule.id %]"><i class="fa fa-check"></i> Save</button>
280
                    <a href="?"><button class="btn btn-mini" title="Cancel" ><i class="fa fa-times"></i> Cancel</button></a>
281
                </td>
282
                <td></td>
283
            [% ELSE %]
284
                <td>[% rule.id %]</td>
285
                <td>[% rule.tagfield %]</td>
286
                <td>[% rule.module %]</td>
287
                <td>[% rule.filter %]</td>
288
                <td>[% rule.on_new %]</td>
289
                <td>[% rule.on_appended %]</td>
290
                <td>[% rule.on_removed %]</td>
291
                <td>[% rule.on_deleted %]</td>
292
                <td class="actions">
293
                    <a href="?op=remove&id=[% rule.id %]" title="Delete" class="btn btn-mini"><i class="fa fa-trash"></i> Delete</a>
294
                    <a href="?op=edit&id=[% rule.id %]" title="Edit" class="btn btn-mini"><i class="fa fa-pencil"></i> Edit</a>
295
                </td>
296
                <td>
297
                    [% IF rule.remove %]
298
                    <input type="checkbox" name="batchremove" value="[% rule.id %]" checked="checked"/>
299
                    [% ELSE %]
300
                    <input type="checkbox" name="batchremove" value="[% rule.id %]"/>
301
                    [% END %]
302
                </td>
303
            [% END %]
304
            </tr>
305
        [% END %]
306
    </tbody>
307
</table>
308
</form>
309
310
<form action="[% script_name %]" method="post">
311
<input type="hidden" name="op" value="redo-matching" />
312
</form>
313
314
</div>
315
</div>
316
<div class="yui-b">
317
[% INCLUDE 'admin-menu.inc' %]
318
</div>
319
</div>
320
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref (+10 lines)
Lines 259-261 Cataloging: Link Here
259
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
259
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
260
            - "<br/>"
260
            - "<br/>"
261
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
261
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
262
        -
263
            - When importing records
264
            - pref: MARCPermissions
265
              choices:
266
                  yes: "use"
267
                  no: "don't use"
268
            - MARC permissions rules to decide which action to take for each field.
269
            - When <b>add_or_correct</b>, consider a field modification to be a correction if the old and new value has a similarity of
270
            - pref: MARCPermissionsCorrectionSimilarity
271
            - %. If similarity falls below this value, the new value will be treated as a new field.
(-)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 558-563 function Changefwk() { Link Here
558
[% END %]
558
[% END %]
559
        <input type="hidden" name="op" value="addbiblio" />
559
        <input type="hidden" name="op" value="addbiblio" />
560
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode %]" />
560
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode %]" />
561
        <input type="hidden" name="z3950" value="[% z3950 %]" />
561
        <input type="hidden" name="biblionumber" value="[% biblionumber %]" />
562
        <input type="hidden" name="biblionumber" value="[% biblionumber %]" />
562
        <input type="hidden" name="breedingid" value="[% breedingid %]" />
563
        <input type="hidden" name="breedingid" value="[% breedingid %]" />
563
        <input type="hidden" name="changed_framework" value="" />
564
        <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 451-457 RECORD: while ( ) { Link Here
451
                    $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
451
                    $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
452
                };
452
                };
453
                if ($update) {
453
                if ($update) {
454
                    eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
454
                    eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber), {context => {source => 'bulkmarcimport'}}) };
455
                    if ($@) {
455
                    if ($@) {
456
                        warn "ERROR: Edit biblio $biblionumber failed: $@\n";
456
                        warn "ERROR: Edit biblio $biblionumber failed: $@\n";
457
                        printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
457
                        printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
Lines 844-847 If not specified, no MARC modification templates are used (default). Link Here
844
=back
844
=back
845
845
846
=cut
846
=cut
847
(-)a/misc/migration_tools/import_lexile.pl (-1 / +2 lines)
Lines 153-158 while ( my $row = $csv->getline_hr($fh) ) { Link Here
153
    foreach my $biblionumber (@biblionumbers) {
153
    foreach my $biblionumber (@biblionumbers) {
154
        $counter++;
154
        $counter++;
155
        my $record = GetMarcBiblio({ biblionumber => $biblionumber });
155
        my $record = GetMarcBiblio({ biblionumber => $biblionumber });
156
        my $frameworkcode = GetFrameworkCode($biblionumber);
156
157
157
        if ($verbose) {
158
        if ($verbose) {
158
            say "Found matching record! Biblionumber: $biblionumber";
159
            say "Found matching record! Biblionumber: $biblionumber";
Lines 202-208 while ( my $row = $csv->getline_hr($fh) ) { Link Here
202
            $record->append_fields($field);
203
            $record->append_fields($field);
203
        }
204
        }
204
205
205
        ModBiblio( $record, $biblionumber ) unless ( $test );
206
        ModBiblio( $record, $biblionumber, $frameworkcode, {context => {source => 'import_lexile'}} ) unless ( $test );
206
    }
207
    }
207
208
208
}
209
}
(-)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 158-164 if ( $op eq 'form' ) { Link Here
158
    my ( $job );
158
    my ( $job );
159
    if ( $runinbackground ) {
159
    if ( $runinbackground ) {
160
        my $job_size = scalar( @record_ids );
160
        my $job_size = scalar( @record_ids );
161
        $job = C4::BackgroundJob->new( $sessionID, "FIXME", '/cgi-bin/koha/tools/batch_record_modification.pl', $job_size );
161
        $job = C4::BackgroundJob->new( $sessionID, "FIXME", $ENV{SCRIPT_NAME}, $job_size );
162
        my $job_id = $job->id;
162
        my $job_id = $job->id;
163
        if (my $pid = fork) {
163
        if (my $pid = fork) {
164
            $dbh->{InactiveDestroy}  = 1;
164
            $dbh->{InactiveDestroy}  = 1;
Lines 170-176 if ( $op eq 'form' ) { Link Here
170
        } elsif (defined $pid) {
170
        } elsif (defined $pid) {
171
            close STDOUT;
171
            close STDOUT;
172
        } else {
172
        } else {
173
            warn "fork failed while attempting to run tools/batch_record_modification.pl as a background job";
173
            warn "fork failed while attempting to run $ENV{'SCRIPT_NAME'} as a background job";
174
            exit 0;
174
            exit 0;
175
        }
175
        }
176
    }
176
    }
Lines 194-200 if ( $op eq 'form' ) { Link Here
194
                my $record = GetMarcBiblio({ biblionumber => $biblionumber });
194
                my $record = GetMarcBiblio({ biblionumber => $biblionumber });
195
                ModifyRecordWithTemplate( $mmtid, $record );
195
                ModifyRecordWithTemplate( $mmtid, $record );
196
                my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
196
                my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
197
                ModBiblio( $record, $biblionumber, $frameworkcode );
197
                my ($member) = Koha::Patrons->find($loggedinuser);
198
                ModBiblio( $record, $biblionumber, $frameworkcode,
199
                    {
200
                        source => 'batchmod',
201
                        category => $member->{'category_type'},
202
                        borrower => $loggedinuser
203
                    }
204
                );
198
            };
205
            };
199
            if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
206
            if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
200
                push @messages, {
207
                push @messages, {
201
- 

Return to bug 14957