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

(-)a/C4/Biblio.pm (-9 / +606 lines)
Lines 57-62 BEGIN { Link Here
57
        PrepHostMarcField
57
        PrepHostMarcField
58
        CountItemsIssued
58
        CountItemsIssued
59
        CountBiblioInOrders
59
        CountBiblioInOrders
60
        GetMarcPermissionsRules
61
        GetMarcPermissionsModules
62
        ModMarcPermissionsRule
63
        AddMarcPermissionsRule
64
        DelMarcPermissionsRule
60
        ModBiblio
65
        ModBiblio
61
        ModZebra
66
        ModZebra
62
        UpdateTotalIssues
67
        UpdateTotalIssues
Lines 64-69 BEGIN { Link Here
64
        DelBiblio
69
        DelBiblio
65
        BiblioAutoLink
70
        BiblioAutoLink
66
        LinkBibHeadingsToAuthorities
71
        LinkBibHeadingsToAuthorities
72
        ApplyMarcPermissions
67
        TransformMarcToKoha
73
        TransformMarcToKoha
68
        TransformHtmlToMarc
74
        TransformHtmlToMarc
69
        TransformHtmlToXml
75
        TransformHtmlToXml
Lines 110-116 use Koha::Libraries; Link Here
110
116
111
use vars qw($debug $cgi_debug);
117
use vars qw($debug $cgi_debug);
112
118
113
114
=head1 NAME
119
=head1 NAME
115
120
116
C4::Biblio - cataloging management functions
121
C4::Biblio - cataloging management functions
Lines 240-246 sub AddBiblio { Link Here
240
245
241
=head2 ModBiblio
246
=head2 ModBiblio
242
247
243
  ModBiblio( $record,$biblionumber,$frameworkcode);
248
  ModBiblio($record, $biblionumber, $frameworkcode, $options);
244
249
245
Replace an existing bib record identified by C<$biblionumber>
250
Replace an existing bib record identified by C<$biblionumber>
246
with one supplied by the MARC::Record object C<$record>.  The embedded
251
with one supplied by the MARC::Record object C<$record>.  The embedded
Lines 261-267 Returns 1 on success 0 on failure Link Here
261
=cut
266
=cut
262
267
263
sub ModBiblio {
268
sub ModBiblio {
264
    my ( $record, $biblionumber, $frameworkcode ) = @_;
269
    my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
270
    $options //= {};
271
265
    if (!$record) {
272
    if (!$record) {
266
        carp 'No record passed to ModBiblio';
273
        carp 'No record passed to ModBiblio';
267
        return 0;
274
        return 0;
Lines 293-298 sub ModBiblio { Link Here
293
300
294
    _strip_item_fields($record, $frameworkcode);
301
    _strip_item_fields($record, $frameworkcode);
295
302
303
    # apply permissions
304
    if (C4::Context->preference('MARCPermissions') && $biblionumber && defined $options && exists $options->{'context'}) {
305
        $record = ApplyMarcPermissions({
306
                biblionumber => $biblionumber,
307
                record => $record,
308
                filter => $options->{'context'},
309
            }
310
        );
311
    }
312
296
    # update biblionumber and biblioitemnumber in MARC
313
    # update biblionumber and biblioitemnumber in MARC
297
    # FIXME - this is assuming a 1 to 1 relationship between
314
    # FIXME - this is assuming a 1 to 1 relationship between
298
    # biblios and biblioitems
315
    # biblios and biblioitems
Lines 303-315 sub ModBiblio { Link Here
303
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
320
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
304
321
305
    # load the koha-table data object
322
    # load the koha-table data object
306
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
323
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode, undef, $biblionumber);
307
324
308
    # update MARC subfield that stores biblioitems.cn_sort
325
    # update MARC subfield that stores biblioitems.cn_sort
309
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
326
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
310
327
311
    # update the MARC record (that now contains biblio and items) with the new record data
328
    # update the MARC record (that now contains biblio and items) with the new record data
312
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
329
    ModBiblioMarc( $record, $biblionumber, $frameworkcode );
313
330
314
    # modify the other koha tables
331
    # modify the other koha tables
315
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
332
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 2525-2531 sub TransformHtmlToMarc { Link Here
2525
2542
2526
=head2 TransformMarcToKoha
2543
=head2 TransformMarcToKoha
2527
2544
2528
    $result = TransformMarcToKoha( $record, undef, $limit )
2545
    $result = TransformMarcToKoha($record, undef, $biblionumber, $option)
2529
2546
2530
Extract data from a MARC bib record into a hashref representing
2547
Extract data from a MARC bib record into a hashref representing
2531
Koha biblio, biblioitems, and items fields.
2548
Koha biblio, biblioitems, and items fields.
Lines 2536-2542 hash_ref. Link Here
2536
=cut
2553
=cut
2537
2554
2538
sub TransformMarcToKoha {
2555
sub TransformMarcToKoha {
2539
    my ( $record, $frameworkcode, $limit_table ) = @_;
2556
    my ( $record, $frameworkcode, $limit_table, $biblionumber ) = @_;
2540
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2557
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2541
    $limit_table //= q{};
2558
    $limit_table //= q{};
2542
2559
Lines 3223-3229 sub _koha_delete_biblio_metadata { Link Here
3223
3240
3224
=head2 ModBiblioMarc
3241
=head2 ModBiblioMarc
3225
3242
3226
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3243
  &ModBiblioMarc($newrec, $biblionumber, $frameworkcode, $options);
3227
3244
3228
Add MARC XML data for a biblio to koha
3245
Add MARC XML data for a biblio to koha
3229
3246
Lines 3550-3557 sub RemoveAllNsb { Link Here
3550
    return $record;
3567
    return $record;
3551
}
3568
}
3552
3569
3553
1;
3570
=head2 ApplyMarcPermissions
3571
3572
    my $record = ApplyMarcPermissions($arguments)
3573
3574
Applies marc permission rules to a record.
3575
3576
C<$arguments> is expected to be a hashref with below keys defined.
3577
3578
=over 4
3579
3580
=item C<biblionumber>
3581
biblionumber of old record
3582
3583
=item C<record>
3584
record that will modify old record
3585
3586
=item C<frameworkcode>
3587
only tags included in framework will be processed
3588
3589
=item C<filter>
3590
hashref containing at least one filter module from the marc_permissions_modules
3591
table in form {module => filter}. Three predefined filter modules exists:
3592
3593
    * source
3594
    * category
3595
    * borrower
3596
3597
=item C<log>
3598
optional reference to array that will be filled with rule evaluation log
3599
entries.
3600
3601
=item C<nolog>
3602
optional boolean which when true disables logging to action log.
3603
3604
=back
3605
3606
Returns:
3607
3608
=over 4
3609
3610
=item C<$record>
3611
3612
new MARC record based on C<record> with C<filter> applied. If no old
3613
record for C<biblionumber> can be found, C<record> is returned unchanged.
3614
Default action when no matching filter found is to leave old record unchanged.
3615
3616
=back
3617
3618
=cut
3619
3620
sub ApplyMarcPermissions {
3621
    my ($arguments) = @_;
3622
    my $biblionumber = $arguments->{biblionumber};
3623
    my $incoming_record = $arguments->{record};
3624
3625
    if ( !$biblionumber ) {
3626
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3627
        return;
3628
    }
3629
    if ( !$incoming_record ) {
3630
        carp 'ApplyMarcPermissions called on undefined record';
3631
        return;
3632
    }
3633
    my $old_record = GetMarcBiblio({ biblionumber => $biblionumber });
3634
3635
    my $merge_rules = undef;
3636
    if ($old_record && $arguments->{filter} && ($merge_rules = GetMarcPermissions($arguments->{filter}))) {
3637
        return MergeRecords($old_record, $incoming_record, $merge_rules);
3638
    }
3639
    return $incoming_record;
3640
}
3641
3642
sub MergeRecords {
3643
    my ($old_record, $incoming_record, $merge_rules) = @_;
3644
    my $is_matching_regex = sub {
3645
        my ( $tag, $m ) = @_;
3646
3647
        # tag is not exactly same as possible regex
3648
        $tag ne $m &&
3649
3650
        # wildcard
3651
        $m ne '*' &&
3652
3653
        # valid tagDataType
3654
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3655
        ([1-9A-z][\dA-z]{2})$/x &&
3656
3657
        # nor valid controltagDataType
3658
        $m !~ /00[1-9A-Za-z]{1}/ &&
3659
3660
        # so we try it as a regex
3661
        $tag =~ /^$m$/
3662
    };
3663
3664
    my $fields_by_tag = sub {
3665
        my ($record) = @_;
3666
        my $fields = {};
3667
        foreach my $field ($record->fields()) {
3668
            $fields->{$field->tag()} //= [];
3669
            push @{$fields->{$field->tag()}}, $field;
3670
        }
3671
        return $fields;
3672
    };
3673
3674
    my $hash_field_data = sub {
3675
        my ($field) = @_;
3676
        my $indicators = join("\x1E", map { $field->indicator($_) } (1, 2));
3677
        return $indicators . "\x1E" . join("\x1E", sort map { join "\x1E", @{$_} } $field->subfields());
3678
    };
3679
3680
    my $diff_by_key = sub {
3681
        my ($a, $b) = @_;
3682
        my @removed;
3683
        my @intersecting;
3684
        my @added;
3685
        my %keys_index = map { $_ => undef } (keys %{$a}, keys %{$b});
3686
        foreach my $key (keys %keys_index) {
3687
            if ($a->{$key} && $b->{$key}) {
3688
                push @intersecting, $a->{$key};
3689
            }
3690
            elsif ($a->{$key}) {
3691
                push @removed, $a->{$key};
3692
            }
3693
            else {
3694
                push @added, $b->{$key};
3695
            }
3696
        }
3697
        return (\@removed, \@intersecting, \@added);
3698
    };
3699
3700
    my $get_matching_field_rule = sub {
3701
        my ($tag) = @_;
3702
        my $matched_rule = undef;
3703
        # Exact match takes precedence
3704
        if (exists $merge_rules->{$tag}) {
3705
            $matched_rule = $merge_rules->{$tag};
3706
        }
3707
        else {
3708
            # TODO: sorty by module/weight rule id or something, grab first matching via listutils thingy
3709
            my @matching_rules = map { $merge_rules->{$_} } grep { $is_matching_regex->($tag, $_) } sort keys %{$merge_rules};
3710
            # TODO: fix
3711
            if (@matching_rules) {
3712
                $matched_rule = pop @matching_rules;
3713
            }
3714
            elsif($merge_rules->{'*'}) {
3715
                $matched_rule = $merge_rules->{'*'};
3716
            }
3717
        }
3718
        return $matched_rule;
3719
    };
3720
3721
    my $merged_record = MARC::Record->new();
3722
    my @merged_record_fields;
3723
3724
    # Leader is always overwritten, or kept???
3725
    $merged_record->leader($incoming_record->leader());
3726
3727
    my $current_fields = $fields_by_tag->($old_record);
3728
    my $incoming_fields = $fields_by_tag->($incoming_record);
3729
3730
    # First we get all new incoming control fields
3731
    my @new_field_tags = grep { !(exists $current_fields->{$_}) } keys %{$incoming_fields};
3732
3733
    foreach my $tag (@new_field_tags) {
3734
        my $rule = $get_matching_field_rule->($tag) // {
3735
            on_new => {'action' => 'skip', 'rule' => 0}
3736
        };
3737
        if (
3738
            $rule->{on_new}->{action} eq 'add' ||
3739
            $rule->{on_new}->{action} eq 'overwrite' # ???
3740
        ) { # Or could just be write/protect?
3741
            # Hmm, only one control field possible??
3742
            push @merged_record_fields, @{$incoming_fields->{$tag}};
3743
        }
3744
    }
3745
3746
    # Then we get all control fields no longer present in incoming fields
3747
    # (removed)
3748
    my @deleted_field_tags = grep { !(exists $incoming_fields->{$_}) } keys %{$current_fields};
3749
    foreach my $tag (@deleted_field_tags) {
3750
        my $rule = $get_matching_field_rule->($tag) // {
3751
            on_deleted => {'action' => 'skip', 'rule' => 0}
3752
        };
3753
        if ($rule->{on_deleted}->{action} eq 'skip') {
3754
            push @merged_record_fields, @{$current_fields->{$tag}};
3755
        }
3756
    }
3757
3758
    # Then we get the intersection of control fields, present both in
3759
    # current and incoming record (possibly to be overwritten)
3760
    my @common_field_tags = grep { exists $incoming_fields->{$_} } keys %{$current_fields};
3761
    foreach my $tag (@common_field_tags) {
3762
        # Is control field
3763
        my $rule = $get_matching_field_rule->($tag) // {
3764
            on_removed => {'action' => 'skip', 'rule' => 0},
3765
            on_appended => {'action' => 'skip', 'rule' => 0}
3766
        };
3767
        if ($tag < 10) {
3768
            # on_existing = on_match
3769
            if ($rule->{on_appended}->{action} eq 'skip') { # TODO: replace with "protect", "keep"
3770
                push @merged_record_fields, @{$current_fields->{$tag}};
3771
            }
3772
            elsif ($rule->{on_appended}->{action} eq 'append') {
3773
                push @merged_record_fields, @{$incoming_fields->{$tag}};
3774
            }
3775
            if (
3776
                $rule->{on_appended}->{action} eq 'append' &&
3777
                $rule->{on_removed}->{action} eq 'skip'
3778
            ) {
3779
                #TODO: This is an invalid combination for control fields, warn!!
3780
                # Or should perform client/server side validation to prevent this choice
3781
            }
3782
        }
3783
        else {
3784
            # Compute intersection and diff using field data
3785
            my %current_fields_by_data = map { $hash_field_data->($_) => $_ } @{$current_fields->{$tag}};
3786
            my %incoming_fields_by_data = map { $hash_field_data->($_) => $_ } @{$incoming_fields->{$tag}};
3787
            my ($current_fields_only, $common_fields, $incoming_fields_only) = $diff_by_key->(\%current_fields_by_data, \%incoming_fields_by_data);
3788
3789
            # First add common fields (intersection)
3790
            # Unchanged
3791
            if (@{$common_fields}) {
3792
                push @merged_record_fields, @{$common_fields};
3793
            }
3794
            # Removed
3795
            if (@{$current_fields_only}) {
3796
                if ($rule->{on_removed}->{action} eq 'skip') {
3797
                    push @merged_record_fields, @{$current_fields_only};
3798
                }
3799
            }
3800
            # Appended
3801
            if (@{$incoming_fields_only}) {
3802
                if ($rule->{on_appended}->{action} eq 'append') {
3803
                    push @merged_record_fields, @{$incoming_fields_only};
3804
                }
3805
            }
3806
        }
3807
    }
3808
    if ($#merged_record_fields != 0) {
3809
        $merged_record->insert_fields_ordered(@merged_record_fields);
3810
    }
3811
    return $merged_record;
3812
}
3813
3814
=head2 GetMarcPermissions
3815
3816
    my $marc_permissions = GetMarcPermissions()
3817
3818
Loads MARC field permissions from the marc_permissions table.
3819
3820
Returns:
3821
3822
=over 4
3823
3824
=item C<$marc_permissions>
3825
3826
hashref with permissions structure for use with GetMarcPermissionsAction.
3827
3828
=back
3829
3830
=cut
3831
3832
sub GetMarcPermissions {
3833
    my ($filter) = @_;
3834
    my $dbh = C4::Context->dbh;
3835
    my $rule_count = 0;
3836
    my $modules = GetMarcPermissionsModules();
3837
    # We only care about modules included in the context/filter
3838
    # TODO: Perhaps make sure source => '*' is default?
3839
    my @filter_modules = grep { exists $filter->{$_->{name}} } @{$modules};
3840
3841
    my $cache = Koha::Caches->get_instance();
3842
    my $permissions = $cache->get_from_cache('marc_permissions', { unsafe => 1 });
3843
3844
    if (!$permissions) {
3845
        my $query = '
3846
            SELECT `marc_permissions`.*,
3847
                `marc_permissions_modules`.`name`,
3848
                `marc_permissions_modules`.`description`,
3849
                `marc_permissions_modules`.`specificity`
3850
                FROM `marc_permissions`
3851
                LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
3852
                ORDER BY `marc_permissions_modules`.`specificity`, `id`
3853
        ';
3854
        my $sth = $dbh->prepare($query);
3855
        $sth->execute();
3856
        while (my $perm = $sth->fetchrow_hashref) {
3857
            my $target = ($permissions->{$perm->{name}}->{$perm->{filter}}->{$perm->{tagfield}} //= {});
3858
            foreach my $event (GetMarcPermissionEvents()) {
3859
                $target->{$event} = { action => $perm->{$event}, rule => $perm->{'id'} };
3860
            }
3861
        }
3862
        $cache->set_in_cache('marc_permissions', $permissions);
3863
    }
3864
3865
    my $filtered_permissions = undef;
3866
    foreach my $module (@filter_modules) {
3867
        if (
3868
            exists $permissions->{$module->{name}} &&
3869
            exists $permissions->{$module->{name}}->{$filter->{$module->{name}}}
3870
        ) {
3871
            # TODO: Support multiple overlapping filters/context??
3872
            $filtered_permissions = $permissions->{$module->{name}}->{$filter->{$module->{name}}};
3873
            last;
3874
        }
3875
    }
3876
    if (!$filtered_permissions) {
3877
        # No perms matching specific context conditions found, try wildcard value for each active context
3878
        foreach my $module (@filter_modules) {
3879
            if (exists $permissions->{$module->{name}}->{'*'}) {
3880
                $filtered_permissions = $permissions->{$module->{name}}->{'*'};
3881
                last;
3882
            }
3883
        }
3884
    }
3885
    return $filtered_permissions;
3886
}
3887
3888
# TODO: Use this in GetMarcPermissions
3889
=head2 GetMarcPermissionsRules
3890
3891
    my $rules = GetMarcPermissionsRules()
3892
3893
Returns:
3894
3895
=over 4
3896
3897
=item C<$rules>
3898
3899
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
3900
table in order of module specificity and rule id.
3901
3902
=back
3903
3904
=cut
3905
3906
sub GetMarcPermissionsRules {
3907
    my $dbh = C4::Context->dbh;
3908
    my @rules = ();
3909
3910
    my $query = '
3911
    SELECT `marc_permissions`.`id`,
3912
           `marc_permissions`.`tagfield`,
3913
           `marc_permissions`.`filter`,
3914
           `marc_permissions`.`on_new`,
3915
           `marc_permissions`.`on_appended`,
3916
           `marc_permissions`.`on_removed`,
3917
           `marc_permissions`.`on_deleted`,
3918
           `marc_permissions_modules`.`name` as  `module`,
3919
           `marc_permissions_modules`.`description`,
3920
           `marc_permissions_modules`.`specificity`
3921
    FROM `marc_permissions`
3922
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
3923
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
3924
    ';
3925
    my $sth = $dbh->prepare($query);
3926
    $sth->execute();
3927
    while ( my $row = $sth->fetchrow_hashref ) {
3928
        push(@rules, $row);
3929
    }
3930
3931
    return wantarray ? @rules : \@rules;
3932
}
3933
3934
=head2 GetMarcPermissionsModules
3935
3936
    my $modules = GetMarcPermissionsModules()
3937
3938
Returns:
3939
3940
=over 4
3941
3942
=item C<$modules>
3943
3944
array (in list context, arrayref otherwise) of hashrefs from
3945
marc_permissions_modules table in order of specificity.
3946
3947
=back
3948
3949
=cut
3554
3950
3951
sub GetMarcPermissionsModules {
3952
    my $dbh = C4::Context->dbh;
3953
    my @modules = ();
3954
3955
    my $query = '
3956
    SELECT *
3957
    FROM `marc_permissions_modules`
3958
    ORDER BY `specificity` DESC
3959
    ';
3960
    my $sth = $dbh->prepare($query);
3961
    $sth->execute();
3962
    while ( my $row = $sth->fetchrow_hashref ) {
3963
        push(@modules, $row);
3964
    }
3965
3966
    return wantarray ? @modules : \@modules;
3967
}
3968
3969
sub GetMarcPermissionEvents {
3970
    return ('on_new', 'on_appended', 'on_removed', 'on_deleted');
3971
}
3972
3973
=head2 ModMarcPermissionsRule
3974
3975
    my $success = ModMarcPermissionsRule($id, $fields)
3976
3977
Modifies rule in the marc_permissions table.
3978
3979
=over 4
3980
3981
=item C<$id>
3982
3983
rule id to modify
3984
3985
=item C<$fields>
3986
3987
hashref defining the table fields
3988
3989
      * tagfield - required
3990
      * module - required
3991
      * filter - required
3992
      * on_new - required
3993
      * on_appended - required
3994
      * on_removed - required
3995
      * on_deleted - required
3996
3997
=back
3998
3999
Returns:
4000
4001
=over 4
4002
4003
=item C<$success>
4004
4005
undef if an error occurs, otherwise true.
4006
4007
=back
4008
4009
=cut
4010
4011
sub ModMarcPermissionsRule {
4012
    my ($id, $f) = @_;
4013
    my $dbh = C4::Context->dbh;
4014
4015
    my $query = '
4016
    UPDATE `marc_permissions`
4017
    SET
4018
      tagfield = ?,
4019
      module = ?,
4020
      filter = ?,
4021
      on_new = ?,
4022
      on_appended = ?,
4023
      on_removed = ?,
4024
      on_deleted = ?
4025
    WHERE
4026
      id = ?
4027
    ';
4028
    my $sth = $dbh->prepare($query);
4029
    my $result = $sth->execute (
4030
        $f->{tagfield},
4031
        $f->{module},
4032
        $f->{filter},
4033
        $f->{on_new},
4034
        $f->{on_appended},
4035
        $f->{on_removed},
4036
        $f->{on_deleted},
4037
        $id
4038
    );
4039
    ClearMarcPermissionsRulesCache();
4040
    return $result;
4041
}
4042
4043
sub ClearMarcPermissionsRulesCache {
4044
    my $cache = Koha::Caches->get_instance();
4045
    $cache->clear_from_cache('marc_permissions');
4046
}
4047
4048
=head2 AddMarcPermissionsRule
4049
4050
    my $success = AddMarcPermissionsRule($fields)
4051
4052
Add rule to the marc_permissions table.
4053
4054
=over 4
4055
4056
=item C<$fields>
4057
4058
hashref defining the table fields
4059
4060
      tagfield - required
4061
      module - required
4062
      filter - required
4063
      on_new - required
4064
      on_appended - required
4065
      on_removed - required
4066
      on_deleted - required
4067
4068
=back
4069
4070
Returns:
4071
4072
=over 4
4073
4074
=item C<$success>
4075
4076
undef if an error occurs, otherwise true.
4077
4078
=back
4079
4080
=cut
4081
4082
sub AddMarcPermissionsRule {
4083
    my $f = shift;
4084
    my $dbh = C4::Context->dbh;
4085
    my $query = '
4086
    INSERT INTO `marc_permissions`
4087
    (
4088
      tagfield,
4089
      module,
4090
      filter,
4091
      on_new,
4092
      on_appended,
4093
      on_removed,
4094
      on_deleted
4095
    )
4096
    VALUES (?, ?, ?, ?, ?, ?, ?)
4097
    ';
4098
    my $sth = $dbh->prepare($query);
4099
    my $result = $sth->execute (
4100
        $f->{tagfield},
4101
        $f->{module},
4102
        $f->{filter},
4103
        $f->{on_new},
4104
        $f->{on_appended},
4105
        $f->{on_removed},
4106
        $f->{on_deleted}
4107
    );
4108
    ClearMarcPermissionsRulesCache();
4109
    return $result;
4110
}
4111
4112
=head2 DelMarcPermissionsRule
4113
4114
    my $success = DelMarcPermissionsRule($id)
4115
4116
Deletes rule from the marc_permissions table.
4117
4118
=over 4
4119
4120
=item C<$id>
4121
4122
rule id to delete
4123
4124
=back
4125
4126
Returns:
4127
4128
=over 4
4129
4130
=item C<$success>
4131
4132
undef if an error occurs, otherwise true.
4133
4134
=back
4135
4136
=cut
4137
4138
sub DelMarcPermissionsRule {
4139
    my $id = shift;
4140
    my $dbh = C4::Context->dbh;
4141
    my $query = '
4142
    DELETE FROM `marc_permissions`
4143
    WHERE
4144
      id = ?
4145
    ';
4146
    my $sth = $dbh->prepare($query);
4147
    my $result = $sth->execute($id);
4148
    ClearMarcPermissionsRulesCache();
4149
    return $result;
4150
}
4151
1;
3555
4152
3556
__END__
4153
__END__
3557
4154
(-)a/C4/ImportBatch.pm (-1 / +1 lines)
Lines 667-673 sub BatchCommitRecords { Link Here
667
                }
667
                }
668
                $oldxml = $old_marc->as_xml($marc_type);
668
                $oldxml = $old_marc->as_xml($marc_type);
669
669
670
                ModBiblio($marc_record, $recordid, $oldbiblio->frameworkcode);
670
                ModBiblio($marc_record, $recordid, $oldbiblio->frameworkcode, {context => {source => 'batchimport'}});
671
                $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
671
                $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
672
672
673
                if ($item_result eq 'create_new' || $item_result eq 'replace') {
673
                if ($item_result eq 'create_new' || $item_result eq 'replace') {
(-)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 832-838 if ( $op eq "addbiblio" ) { Link Here
832
    if ( !$duplicatebiblionumber or $confirm_not_duplicate ) {
833
    if ( !$duplicatebiblionumber or $confirm_not_duplicate ) {
833
        my $oldbibitemnum;
834
        my $oldbibitemnum;
834
        if ( $is_a_modif ) {
835
        if ( $is_a_modif ) {
835
            ModBiblio( $record, $biblionumber, $frameworkcode );
836
            my $member = Koha::Patrons->find($loggedinuser);
837
            ModBiblio( $record, $biblionumber, $frameworkcode, {
838
                    context => {
839
                        source => $z3950 ? 'z39.50' : 'intranet',
840
                        category => $member->{'category_type'},
841
                        borrower => $loggedinuser
842
                    }
843
                }
844
            );
836
        }
845
        }
837
        else {
846
        else {
838
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
847
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
Lines 925-930 elsif ( $op eq "delete" ) { Link Here
925
    $template->param(
934
    $template->param(
926
        biblionumberdata => $biblionumber,
935
        biblionumberdata => $biblionumber,
927
        op               => $op,
936
        op               => $op,
937
        z3950            => $z3950
928
    );
938
    );
929
    if ( $op eq "duplicate" ) {
939
    if ( $op eq "duplicate" ) {
930
        $biblionumber = "";
940
        $biblionumber = "";
(-)a/help.pl (+7 lines)
Lines 19-24 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
use CGI qw ( -utf8 );
21
use CGI qw ( -utf8 );
22
use C4::Biblio;
22
23
23
use C4::Auth;
24
use C4::Auth;
24
use C4::Context;
25
use C4::Context;
Lines 45-47 my $language = C4::Languages::getlanguage( $query ); Link Here
45
my $manual_url = Koha::Manual::get_url($refer, $language);
46
my $manual_url = Koha::Manual::get_url($refer, $language);
46
47
47
print $query->redirect($manual_url);
48
print $query->redirect($manual_url);
49
50
my $rules = GetMarcPermissionsRules();
51
my $modules = GetMarcPermissionsModules();
52
$template->param( rules => $rules, modules => $modules );
53
54
output_html_with_http_headers $query, "", $template->output;
(-)a/installer/data/mysql/atomicupdate/bug_14957-marc-permissions-syspref.sql (+2 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');
(-)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 40-45 Link Here
40
    [% IF Koha.Preference('SearchEngine') == 'Elasticsearch' %]
40
    [% IF Koha.Preference('SearchEngine') == 'Elasticsearch' %]
41
        <li><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></li>
41
        <li><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></li>
42
    [% END %]
42
    [% END %]
43
    <li><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></li>
43
</ul>
44
</ul>
44
45
45
[% IF ( CAN_user_acquisition_period_manage || CAN_user_acquisition_budget_manage || CAN_user_parameters || CAN_user_acquisition_edi_manage ) %]
46
[% IF ( CAN_user_acquisition_period_manage || CAN_user_acquisition_budget_manage || CAN_user_parameters || CAN_user_acquisition_edi_manage ) %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+2 lines)
Lines 93-98 Link Here
93
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt>
93
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt>
94
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd>
94
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd>
95
                    [% END %]
95
                    [% END %]
96
                    <dt><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></dt>
97
                    <dd>Managed MARC field permissions</dd>
96
                </dl>
98
                </dl>
97
99
98
                [% IF ( CAN_user_acquisition_currencies_manage || CAN_user_acquisition_period_manage || CAN_user_acquisition_budget_manage || CAN_user_acquisition_edi_manage ) %]
100
                [% IF ( CAN_user_acquisition_currencies_manage || CAN_user_acquisition_period_manage || CAN_user_acquisition_budget_manage || CAN_user_acquisition_edi_manage ) %]
(-)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 (+7 lines)
Lines 280-282 Cataloging: Link Here
280
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
280
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
281
            - "<br/>"
281
            - "<br/>"
282
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
282
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
283
        -
284
            - When importing records
285
            - pref: MARCPermissions
286
              choices:
287
                  yes: "use"
288
                  no: "don't use"
289
            - MARC permissions rules to decide which action to take for each 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 563-568 function Changefwk() { Link Here
563
[% END %]
563
[% END %]
564
        <input type="hidden" name="op" value="addbiblio" />
564
        <input type="hidden" name="op" value="addbiblio" />
565
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode | html %]" />
565
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode | html %]" />
566
        <input type="hidden" name="z3950" value="[% z3950 | html %]" />
566
        <input type="hidden" name="biblionumber" value="[% biblionumber | html %]" />
567
        <input type="hidden" name="biblionumber" value="[% biblionumber | html %]" />
567
        <input type="hidden" name="breedingid" value="[% breedingid | html %]" />
568
        <input type="hidden" name="breedingid" value="[% breedingid | html %]" />
568
        <input type="hidden" name="changed_framework" value="" />
569
        <input type="hidden" name="changed_framework" value="" />
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/help/marc-permissions.tt (+148 lines)
Line 0 Link Here
1
[% INCLUDE 'help-top.inc' %]
2
3
<h1>Manage MARC field permissions</h1>
4
5
<h3>Rule evaluation</h3>
6
<p>Rules are evaluated from least specific to most specific. This means that a more specific rule will override a less specific rule. <b>*</b> is less specific than a regular expression. A regular expression is less specific than a normal field name (e.g. <b>245</b>). A <i>Subfield tag</i> is more specific than a <i>Tag</i>.</p>
7
<p>To add a field-specific rule (i.e. not a subfield), leave the <i>Subfield tag</i> field blank.</p>
8
9
<h4>Defaults</h4>
10
<p>Default action when no rules exist is to replace the old record with the new record. Same as if <b>MARCPermissions</b> is disabled.</p>
11
<p>Default action when no matching rule is found is to leave a field unchanged (<b>skip</b>). If you wish to changed the default actions for fields and subfields, please add wildcard rules.</p>
12
13
<h4>Wildcards</h4>
14
<p><b>*</b> can be used as a wildcard for <i>Tag</i>, <i>Subfield tag</i> and <i>Filter</i>. <b>*</b> is considered less specific than a non wildcard value, and thus will be overridden by a non wildcard value (e.g. "100", "a", etc).</p>
15
16
<h4>Regular expressions</h4>
17
<p>Regular expressions can be used in both the <i>Tag</i> and <i>Subfield tag</i> fields. Beware though, using regular expressions may create overlapping matches, in which case they will be applied in a sorted order.</p>
18
19
<h4>Example rules</h4>
20
<p>Following is an example rule set in order of specificity.</p>
21
22
<table>
23
    <thead><tr>
24
        <th>Tag</th>
25
        <th>Subfield tag</th>
26
        <th>Module</th>
27
        <th>Filter</th>
28
        <th>Description</th>
29
    </tr></thead>
30
    <tbody><tr>
31
        <td>*</td>
32
        <td></td>
33
        <td>[% modules.0.name %]</td>
34
        <td>*</td>
35
        <td><i>Match any field regardless of [% modules.0.name %]</i></td>
36
    </tr>
37
    <tr>
38
        <td>*</td>
39
        <td>*</td>
40
        <td>[% modules.0.name %]</td>
41
        <td>*</td>
42
        <td><i>Match any subfield regardless of [% modules.0.name %]</i></td>
43
    </tr>
44
    <tr>
45
        <td>245</td>
46
        <td>*</td>
47
        <td>[% modules.0.name %]</td>
48
        <td>z39.50</td>
49
        <td><i>Match any subfield under <b>245</b> when [% modules.0.name %] is <b>z39.50</b></i></td>
50
    </tr>
51
    <tr>
52
        <td>*</td>
53
        <td>b</td>
54
        <td>[% modules.0.name %]</td>
55
        <td>z39.50</td>
56
        <td><i>Match subfield <b>b</b> regardless of field when [% modules.0.name %] is <b>z39.50</b></i></td>
57
    </tr>
58
    <tr>
59
        <td>500</td>
60
        <td>[a-d]</td>
61
        <td>[% modules.0.name %]</td>
62
        <td>z39.50</td>
63
        <td><i>Match subfields <b>a, b, c, d</b> when field is 500 and [% modules.0.name %] is <b>z39.50</b></i></td>
64
    </tr>
65
    <tr>
66
        <td>5..</td>
67
        <td>a</td>
68
        <td>[% modules.0.name %]</td>
69
        <td>z39.50</td>
70
        <td><i>Match subfield <b>a</b> when field matches the regular expression <b>5..</b> (500-599) and [% modules.0.name %] is <b>z39.50</b></i></td>
71
    </tr>
72
    <tr>
73
        <td>245</td>
74
        <td>a</td>
75
        <td>[% modules.0.name %]</td>
76
        <td>z39.50</td>
77
        <td><i>Match subfield <b>a</b> when field is <b>500</b> and [% modules.0.name %] is <b>z39.50</b></i></td>
78
    </tr></tbody>
79
</table>
80
81
<br>
82
83
<h3>Available filter modules</h3>
84
<p>Filters cannot be regular expressions. Please use a single <b>*</b> as a wildcard if need.</p>
85
86
<table>
87
    <thead><tr>
88
        <th>Specificity</th>
89
        <th>Module</th>
90
        <th>Description</th>
91
    </tr></thead>
92
    <tbody>
93
    [% FOREACH module IN modules %]
94
        <tr>
95
            <td>[% module.specificity %]</td>
96
            <td>[% module.name %]</td>
97
            <td>[% module.description %]</td>
98
        </tr>
99
    [% END %]
100
    </tbody>
101
</table>
102
103
<br>
104
105
<h3>Available sources</h3>
106
<p>The following sources currently implement MARCPermissions.</p>
107
108
<table>
109
    <thead><tr>
110
        <th>Name</th>
111
        <th>Description</th>
112
    </tr></thead>
113
    <tbody><tr>
114
        <td>bulkmarcimport</td>
115
        <td>bin/migration_tools/bulkmarcimport.pl</td>
116
    </tr>
117
    <tr>
118
        <td>import_lexile</td>
119
        <td>bin/migration_tools/import_lexile.pl</td>
120
    </tr>
121
    <tr>
122
        <td>z39.50</td>
123
        <td>Import from Z39.50 search in browser</td>
124
    </tr>
125
    <tr>
126
        <td>intranet</td>
127
        <td>Modifications from intranet in browser</td>
128
    </tr>
129
    <tr>
130
        <td>batchmod</td>
131
        <td>Batch record modification in browser</td>
132
    </tr></tbody>
133
</table>
134
135
<br>
136
137
<h3>Indicators</h3>
138
<p>Indicators can be addressed as <b>i1</b> and <b>i2</b> in the <i>Subfield tag</i> field. Some actions might not be available for indicators, in which case they will be disabled.</p>
139
140
<br>
141
142
<h3>Logging</h3>
143
<p>If <b>MARCPermissionsLog</b> is enabled, log entries for each record modification will be available in the <b>Modification log</b> in the <b>Catalog</b> module under the <b>Modify</b> action. This can be very helpful when debugging rule sets.</p>
144
<p>The administration area is where you set all of your preferences for the system. Preference are broken down into several categories, detailed below.</p>
145
146
<p><strong>See the full documentation for Koha in the <a href="http://manual.koha-community.org/[% helpVersion %]/en/">manual</a> (online).</strong></p>
147
148
[% INCLUDE 'help-bottom.inc' %]
(-)a/misc/migration_tools/bulkmarcimport.pl (-1 / +1 lines)
Lines 454-460 RECORD: while ( ) { Link Here
454
                    $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
454
                    $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
455
                };
455
                };
456
                if ($update) {
456
                if ($update) {
457
                    eval { ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
457
                    eval { ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber), {context => {source => 'bulkmarcimport'}} ) };
458
                    if ($@) {
458
                    if ($@) {
459
                        warn "ERROR: Edit biblio $biblionumber failed: $@\n";
459
                        warn "ERROR: Edit biblio $biblionumber failed: $@\n";
460
                        printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
460
                        printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
(-)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 (+279 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
38
# Create a record
39
my $record = MARC::Record->new();
40
$record->append_fields (
41
    MARC::Field->new('008', '12345'),
42
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
43
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
44
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
45
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
46
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
47
);
48
49
# Add record to DB
50
my ($biblionumber, $biblioitemnumber) = AddBiblio($record, '');
51
52
my $modules = GetMarcPermissionsModules();
53
54
##############################################################################
55
# Test overwrite rule
56
my $mod_record = MARC::Record->new();
57
$mod_record->append_fields (
58
    MARC::Field->new('008', '12345'),
59
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
60
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
61
    MARC::Field->new('500', '1','1', 'a' => 'this field has now been changed'),
62
    MARC::Field->new('500', '2','2', 'a' => 'and so have this field'),
63
);
64
65
# Clear MARC permission rules from DB
66
DelMarcPermissionsRule($_->{id}) for GetMarcPermissionsRules();
67
68
# Add MARC permission rules to DB
69
AddMarcPermissionsRule({
70
    module => $modules->[0]->{'id'},
71
    tagfield => '*',
72
    tagsubfield => '',
73
    filter => '*',
74
    on_existing => 'overwrite',
75
    on_new => 'add',
76
    on_removed => 'remove'
77
});
78
79
my @log = ();
80
my $new_record = ApplyMarcPermissions({
81
        biblionumber => $biblionumber,
82
        record => $mod_record,
83
        frameworkcode => '',
84
        filter => {$modules->[0]->{'name'} => 'foo'},
85
        log => \@log
86
    });
87
88
my @a500 = $new_record->field('500');
89
is ($a500[0]->subfield('a'), 'this field has now been changed', 'old field is replaced when overwrite');
90
is ($a500[1]->subfield('a'), 'and so have this field', 'old field is replaced when overwrite');
91
92
##############################################################################
93
# Test remove rule
94
is ($new_record->field('250'), undef, 'removed field is removed');
95
96
##############################################################################
97
# Test skip rule
98
$mod_record = MARC::Record->new();
99
$mod_record->append_fields (
100
    MARC::Field->new('008', '12345'),
101
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
102
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
103
    MARC::Field->new('500', '1','1', 'a' => 'this should not show'),
104
    MARC::Field->new('500', '2','2', 'a' => 'and neither should this'),
105
);
106
107
AddMarcPermissionsRule({
108
    module => $modules->[0]->{'id'},
109
    tagfield => '500',
110
    tagsubfield => '*',
111
    filter => '*',
112
    on_existing => 'skip',
113
    on_new => 'skip',
114
    on_removed => 'skip'
115
});
116
117
@log = ();
118
$new_record = ApplyMarcPermissions({
119
        biblionumber => $biblionumber,
120
        record => $mod_record,
121
        frameworkcode => '',
122
        filter => {$modules->[0]->{'name'} => 'foo'},
123
        log => \@log
124
    });
125
126
@a500 = $new_record->field('500');
127
is ($a500[0]->subfield('a'), 'the lazy programmer jumps over the quick brown tests', 'old field is kept when skip');
128
is ($a500[1]->subfield('a'), 'the quick brown test jumps over the lazy programmers', 'old field is kept when skip');
129
130
##############################################################################
131
# Test add rule
132
$mod_record = MARC::Record->new();
133
$mod_record->append_fields (
134
    MARC::Field->new('008', '12345'),
135
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
136
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
137
    #MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
138
    MARC::Field->new('245', '1','2', 'a' => 'some new fun value'),
139
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
140
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
141
);
142
143
AddMarcPermissionsRule({
144
    module => $modules->[0]->{'id'},
145
    tagfield => '245',
146
    tagsubfield => '*',
147
    filter => '*',
148
    on_existing => 'add',
149
    on_new => 'add',
150
    on_removed => 'skip'
151
});
152
153
154
@log = ();
155
$new_record = ApplyMarcPermissions({
156
        biblionumber => $biblionumber,
157
        record => $mod_record,
158
        frameworkcode => '',
159
        filter => {$modules->[0]->{'name'} => 'foo'},
160
        log => \@log
161
    });
162
163
my @a245 = $new_record->field('245')->subfield('a');
164
is ($a245[0], 'field data for 245 a with indicators 12', 'old field is kept when adding new');
165
is ($a245[1], 'some new fun value', 'new field is added');
166
167
##############################################################################
168
# Test add_or_correct rule
169
$mod_record = MARC::Record->new();
170
$mod_record->append_fields (
171
    MARC::Field->new('008', '12345'),
172
    #MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
173
    MARC::Field->new('100', '','', 'a' => 'a very different value'),
174
    MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'),
175
    #MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
176
    MARC::Field->new('245', '1','2', 'a' => 'Field data for 245 a with indicators 12', 'a' => 'some very different value'),
177
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
178
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
179
);
180
181
AddMarcPermissionsRule({
182
    module => $modules->[0]->{'id'},
183
    tagfield => '(100|245)',
184
    tagsubfield => '*',
185
    filter => '*',
186
    on_existing => 'add_or_correct',
187
    on_new => 'add',
188
    on_removed => 'skip'
189
});
190
191
@log = ();
192
$new_record = ApplyMarcPermissions({
193
        biblionumber => $biblionumber,
194
        record => $mod_record,
195
        frameworkcode => '',
196
        filter => {$modules->[0]->{'name'} => 'foo'},
197
        log => \@log
198
    });
199
200
@a245 = $new_record->field('245')->subfield('a');
201
is ($a245[0], 'Field data for 245 a with indicators 12', 'add_or_correct modifies field when a correction');
202
is ($a245[1], 'some very different value', 'add_or_correct adds field when not a correction');
203
204
my @a100 = $new_record->field('100')->subfield('a');
205
is ($a100[0], 'field data for 100 a without indicators', 'add_or_correct keeps old field when not a correction');
206
is ($a100[1], 'a very different value', 'add_or_correct adds field when not a correction');
207
208
##############################################################################
209
# Test rule evaluation order
210
$mod_record = MARC::Record->new();
211
$mod_record->append_fields (
212
    MARC::Field->new('008', '12345'),
213
    MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'),
214
    MARC::Field->new('250', '','', 'a' => 'take one down, pass it around'),
215
    MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'),
216
    MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'),
217
    MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'),
218
);
219
220
221
DelMarcPermissionsRule($_->{id}) for GetMarcPermissionsRules();
222
223
AddMarcPermissionsRule({
224
    module => $modules->[0]->{'id'},
225
    tagfield => '*',
226
    tagsubfield => '*',
227
    filter => '*',
228
    on_existing => 'skip',
229
    on_new => 'skip',
230
    on_removed => 'skip'
231
});
232
AddMarcPermissionsRule({
233
    module => $modules->[0]->{'id'},
234
    tagfield => '250',
235
    tagsubfield => '*',
236
    filter => '*',
237
    on_existing => 'overwrite',
238
    on_new => 'skip',
239
    on_removed => 'skip'
240
});
241
AddMarcPermissionsRule({
242
    module => $modules->[0]->{'id'},
243
    tagfield => '*',
244
    tagsubfield => 'a',
245
    filter => '*',
246
    on_existing => 'add_or_correct',
247
    on_new => 'skip',
248
    on_removed => 'skip'
249
});
250
AddMarcPermissionsRule({
251
    module => $modules->[0]->{'id'},
252
    tagfield => '250',
253
    tagsubfield => 'a',
254
    filter => '*',
255
    on_existing => 'add',
256
    on_new => 'skip',
257
    on_removed => 'skip'
258
});
259
260
@log = ();
261
$new_record = ApplyMarcPermissions({
262
        biblionumber => $biblionumber,
263
        record => $mod_record,
264
        frameworkcode => '',
265
        filter => {$modules->[0]->{'name'} => 'foo'},
266
        log => \@log
267
    });
268
269
my @rule = grep { $_->{tag} eq '250' and $_->{subfieldcode} eq 'a' } @log;
270
is(scalar @rule, 1, 'only one rule applied');
271
is($rule[0]->{event}.':'.$rule[0]->{action}, 'existing:add', 'most specific rule used');
272
273
my @a250 = $new_record->field('250')->subfield('a');
274
is ($a250[0], '250 bottles of beer on the wall', 'most specific rule is applied, original field kept');
275
is ($a250[1], 'take one down, pass it around', 'most specific rule is applied, new field added');
276
277
$dbh->rollback;
278
279
1;
(-)a/tools/batch_record_modification.pl (-4 / +10 lines)
Lines 159-165 if ( $op eq 'form' ) { Link Here
159
    my ( $job );
159
    my ( $job );
160
    if ( $runinbackground ) {
160
    if ( $runinbackground ) {
161
        my $job_size = scalar( @record_ids );
161
        my $job_size = scalar( @record_ids );
162
        $job = C4::BackgroundJob->new( $sessionID, "FIXME", '/cgi-bin/koha/tools/batch_record_modification.pl', $job_size );
162
        $job = C4::BackgroundJob->new( $sessionID, "FIXME", $ENV{SCRIPT_NAME}, $job_size );
163
        my $job_id = $job->id;
163
        my $job_id = $job->id;
164
        if (my $pid = fork) {
164
        if (my $pid = fork) {
165
            $dbh->{InactiveDestroy}  = 1;
165
            $dbh->{InactiveDestroy}  = 1;
Lines 171-177 if ( $op eq 'form' ) { Link Here
171
        } elsif (defined $pid) {
171
        } elsif (defined $pid) {
172
            close STDOUT;
172
            close STDOUT;
173
        } else {
173
        } else {
174
            warn "fork failed while attempting to run tools/batch_record_modification.pl as a background job";
174
            warn "fork failed while attempting to run $ENV{'SCRIPT_NAME'} as a background job";
175
            exit 0;
175
            exit 0;
176
        }
176
        }
177
    }
177
    }
Lines 195-201 if ( $op eq 'form' ) { Link Here
195
                my $record = GetMarcBiblio({ biblionumber => $biblionumber });
195
                my $record = GetMarcBiblio({ biblionumber => $biblionumber });
196
                ModifyRecordWithTemplate( $mmtid, $record );
196
                ModifyRecordWithTemplate( $mmtid, $record );
197
                my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
197
                my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
198
                ModBiblio( $record, $biblionumber, $frameworkcode );
198
                my ($member) = Koha::Patrons->find($loggedinuser);
199
                ModBiblio( $record, $biblionumber, $frameworkcode,
200
                    {
201
                        source => 'batchmod',
202
                        category => $member->{'category_type'},
203
                        borrower => $loggedinuser
204
                    }
205
                );
199
            };
206
            };
200
            if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
207
            if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
201
                push @messages, {
208
                push @messages, {
202
- 

Return to bug 14957