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

(-)a/C4/Biblio.pm (-8 / +607 lines)
Lines 99-104 BEGIN { Link Here
99
99
100
      &CountItemsIssued
100
      &CountItemsIssued
101
      &CountBiblioInOrders
101
      &CountBiblioInOrders
102
103
      &GetMarcPermissionsRules
104
      &GetMarcPermissionsModules
105
      &ModMarcPermissionsRule
106
      &AddMarcPermissionsRule
107
      &DelMarcPermissionsRule
102
    );
108
    );
103
109
104
    # To modify something
110
    # To modify something
Lines 126-131 BEGIN { Link Here
126
    # they are useful in a few circumstances, so they are exported,
132
    # they are useful in a few circumstances, so they are exported,
127
    # but don't use them unless you are a core developer ;-)
133
    # but don't use them unless you are a core developer ;-)
128
    push @EXPORT, qw(
134
    push @EXPORT, qw(
135
      &ApplyMarcPermissions
129
      &ModBiblioMarc
136
      &ModBiblioMarc
130
    );
137
    );
131
138
Lines 263-269 sub AddBiblio { Link Here
263
270
264
=head2 ModBiblio
271
=head2 ModBiblio
265
272
266
  ModBiblio( $record,$biblionumber,$frameworkcode);
273
  ModBiblio($record, $biblionumber, $frameworkcode, $options);
267
274
268
Replace an existing bib record identified by C<$biblionumber>
275
Replace an existing bib record identified by C<$biblionumber>
269
with one supplied by the MARC::Record object C<$record>.  The embedded
276
with one supplied by the MARC::Record object C<$record>.  The embedded
Lines 284-290 Returns 1 on success 0 on failure Link Here
284
=cut
291
=cut
285
292
286
sub ModBiblio {
293
sub ModBiblio {
287
    my ( $record, $biblionumber, $frameworkcode ) = @_;
294
    my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
295
    $options //= {};
296
288
    if (!$record) {
297
    if (!$record) {
289
        carp 'No record passed to ModBiblio';
298
        carp 'No record passed to ModBiblio';
290
        return 0;
299
        return 0;
Lines 312-317 sub ModBiblio { Link Here
312
321
313
    _strip_item_fields($record, $frameworkcode);
322
    _strip_item_fields($record, $frameworkcode);
314
323
324
    # apply permissions
325
    if (C4::Context->preference('MARCPermissions') && $biblionumber && defined $options && exists $options->{'context'}) {
326
        $record = ApplyMarcPermissions({
327
                biblionumber => $biblionumber,
328
                record => $record,
329
                filter => $options->{'context'},
330
            }
331
        );
332
    }
333
315
    # update biblionumber and biblioitemnumber in MARC
334
    # update biblionumber and biblioitemnumber in MARC
316
    # FIXME - this is assuming a 1 to 1 relationship between
335
    # FIXME - this is assuming a 1 to 1 relationship between
317
    # biblios and biblioitems
336
    # biblios and biblioitems
Lines 322-334 sub ModBiblio { Link Here
322
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
341
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
323
342
324
    # load the koha-table data object
343
    # load the koha-table data object
325
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
344
    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode, undef, $biblionumber);
326
345
327
    # update MARC subfield that stores biblioitems.cn_sort
346
    # update MARC subfield that stores biblioitems.cn_sort
328
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
347
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
329
348
330
    # update the MARC record (that now contains biblio and items) with the new record data
349
    # update the MARC record (that now contains biblio and items) with the new record data
331
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
350
    ModBiblioMarc( $record, $biblionumber, $frameworkcode );
332
351
333
    # modify the other koha tables
352
    # modify the other koha tables
334
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
353
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 2508-2514 sub TransformHtmlToMarc { Link Here
2508
2527
2509
=head2 TransformMarcToKoha
2528
=head2 TransformMarcToKoha
2510
2529
2511
    $result = TransformMarcToKoha( $record, undef, $limit )
2530
    $result = TransformMarcToKoha($record, undef, $biblionumber, $option)
2512
2531
2513
Extract data from a MARC bib record into a hashref representing
2532
Extract data from a MARC bib record into a hashref representing
2514
Koha biblio, biblioitems, and items fields.
2533
Koha biblio, biblioitems, and items fields.
Lines 2519-2525 hash_ref. Link Here
2519
=cut
2538
=cut
2520
2539
2521
sub TransformMarcToKoha {
2540
sub TransformMarcToKoha {
2522
    my ( $record, $frameworkcode, $limit_table ) = @_;
2541
    my ( $record, $frameworkcode, $limit_table, $biblionumber ) = @_;
2523
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2542
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2524
    $limit_table //= q{};
2543
    $limit_table //= q{};
2525
2544
Lines 3206-3212 sub _koha_delete_biblio_metadata { Link Here
3206
3225
3207
=head2 ModBiblioMarc
3226
=head2 ModBiblioMarc
3208
3227
3209
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3228
  &ModBiblioMarc($newrec, $biblionumber, $frameworkcode, $options);
3210
3229
3211
Add MARC XML data for a biblio to koha
3230
Add MARC XML data for a biblio to koha
3212
3231
Lines 3533-3540 sub RemoveAllNsb { Link Here
3533
    return $record;
3552
    return $record;
3534
}
3553
}
3535
3554
3536
1;
3555
=head2 ApplyMarcPermissions
3556
3557
    my $record = ApplyMarcPermissions($arguments)
3558
3559
Applies marc permission rules to a record.
3560
3561
C<$arguments> is expected to be a hashref with below keys defined.
3562
3563
=over 4
3564
3565
=item C<biblionumber>
3566
biblionumber of old record
3567
3568
=item C<record>
3569
record that will modify old record
3570
3571
=item C<frameworkcode>
3572
only tags included in framework will be processed
3573
3574
=item C<filter>
3575
hashref containing at least one filter module from the marc_permissions_modules
3576
table in form {module => filter}. Three predefined filter modules exists:
3577
3578
    * source
3579
    * category
3580
    * borrower
3581
3582
=item C<log>
3583
optional reference to array that will be filled with rule evaluation log
3584
entries.
3585
3586
=item C<nolog>
3587
optional boolean which when true disables logging to action log.
3588
3589
=back
3590
3591
Returns:
3592
3593
=over 4
3594
3595
=item C<$record>
3596
3597
new MARC record based on C<record> with C<filter> applied. If no old
3598
record for C<biblionumber> can be found, C<record> is returned unchanged.
3599
Default action when no matching filter found is to leave old record unchanged.
3600
3601
=back
3602
3603
=cut
3604
3605
sub ApplyMarcPermissions {
3606
    my ($arguments) = @_;
3607
    my $biblionumber = $arguments->{biblionumber};
3608
    my $incoming_record = $arguments->{record};
3609
3610
    if ( !$biblionumber ) {
3611
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3612
        return;
3613
    }
3614
    if ( !$incoming_record ) {
3615
        carp 'ApplyMarcPermissions called on undefined record';
3616
        return;
3617
    }
3618
    my $old_record = GetMarcBiblio({ biblionumber => $biblionumber });
3619
3620
    my $merge_rules = undef;
3621
    if ($old_record && $arguments->{filter} && ($merge_rules = GetMarcPermissions($arguments->{filter}))) {
3622
        return MergeRecords($old_record, $incoming_record, $merge_rules);
3623
    }
3624
    return $incoming_record;
3625
}
3626
3627
sub MergeRecords {
3628
    my ($old_record, $incoming_record, $merge_rules) = @_;
3629
    my $is_matching_regex = sub {
3630
        my ( $tag, $m ) = @_;
3631
3632
        # tag is not exactly same as possible regex
3633
        $tag ne $m &&
3634
3635
        # wildcard
3636
        $m ne '*' &&
3637
3638
        # valid tagDataType
3639
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3640
        ([1-9A-z][\dA-z]{2})$/x &&
3641
3642
        # nor valid controltagDataType
3643
        $m !~ /00[1-9A-Za-z]{1}/ &&
3644
3645
        # so we try it as a regex
3646
        $tag =~ /^$m$/
3647
    };
3648
3649
    my $fields_by_tag = sub {
3650
        my ($record) = @_;
3651
        my $fields = {};
3652
        foreach my $field ($record->fields()) {
3653
            $fields->{$field->tag()} //= [];
3654
            push @{$fields->{$field->tag()}}, $field;
3655
        }
3656
        return $fields;
3657
    };
3658
3659
    my $hash_field_data = sub {
3660
        my ($field) = @_;
3661
        my $indicators = join("\x1E", map { $field->indicator($_) } (1, 2));
3662
        return $indicators . "\x1E" . join("\x1E", sort map { join "\x1E", @{$_} } $field->subfields());
3663
    };
3664
3665
    my $diff_by_key = sub {
3666
        my ($a, $b) = @_;
3667
        my @removed;
3668
        my @intersecting;
3669
        my @added;
3670
        my %keys_index = map { $_ => undef } (keys %{$a}, keys %{$b});
3671
        foreach my $key (keys %keys_index) {
3672
            if ($a->{$key} && $b->{$key}) {
3673
                push @intersecting, $a->{$key};
3674
            }
3675
            elsif ($a->{$key}) {
3676
                push @removed, $a->{$key};
3677
            }
3678
            else {
3679
                push @added, $b->{$key};
3680
            }
3681
        }
3682
        return (\@removed, \@intersecting, \@added);
3683
    };
3684
3685
    my $get_matching_field_rule = sub {
3686
        my ($tag) = @_;
3687
        my $matched_rule = undef;
3688
        # Exact match takes precedence
3689
        if (exists $merge_rules->{$tag}) {
3690
            $matched_rule = $merge_rules->{$tag};
3691
        }
3692
        else {
3693
            # TODO: sorty by module/weight rule id or something, grab first matching via listutils thingy
3694
            my @matching_rules = map { $merge_rules->{$_} } grep { $is_matching_regex->($tag, $_) } sort keys %{$merge_rules};
3695
            # TODO: fix
3696
            if (@matching_rules) {
3697
                $matched_rule = pop @matching_rules;
3698
            }
3699
            elsif($merge_rules->{'*'}) {
3700
                $matched_rule = $merge_rules->{'*'};
3701
            }
3702
        }
3703
        return $matched_rule;
3704
    };
3705
3706
    my $merged_record = MARC::Record->new();
3707
    my @merged_record_fields;
3708
3709
    # Leader is always overwritten, or kept???
3710
    $merged_record->leader($incoming_record->leader());
3711
3712
    my $current_fields = $fields_by_tag->($old_record);
3713
    my $incoming_fields = $fields_by_tag->($incoming_record);
3714
3715
    # First we get all new incoming control fields
3716
    my @new_field_tags = grep { !(exists $current_fields->{$_}) } keys %{$incoming_fields};
3717
3718
    foreach my $tag (@new_field_tags) {
3719
        my $rule = $get_matching_field_rule->($tag) // {
3720
            on_new => {'action' => 'skip', 'rule' => 0}
3721
        };
3722
        if (
3723
            $rule->{on_new}->{action} eq 'add' ||
3724
            $rule->{on_new}->{action} eq 'overwrite' # ???
3725
        ) { # Or could just be write/protect?
3726
            # Hmm, only one control field possible??
3727
            push @merged_record_fields, @{$incoming_fields->{$tag}};
3728
        }
3729
    }
3730
3731
    # Then we get all control fields no longer present in incoming fields
3732
    # (removed)
3733
    my @deleted_field_tags = grep { !(exists $incoming_fields->{$_}) } keys %{$current_fields};
3734
    foreach my $tag (@deleted_field_tags) {
3735
        my $rule = $get_matching_field_rule->($tag) // {
3736
            on_deleted => {'action' => 'skip', 'rule' => 0}
3737
        };
3738
        if ($rule->{on_deleted}->{action} eq 'skip') {
3739
            push @merged_record_fields, @{$current_fields->{$tag}};
3740
        }
3741
    }
3742
3743
    # Then we get the intersection of control fields, present both in
3744
    # current and incoming record (possibly to be overwritten)
3745
    my @common_field_tags = grep { exists $incoming_fields->{$_} } keys %{$current_fields};
3746
    foreach my $tag (@common_field_tags) {
3747
        # Is control field
3748
        my $rule = $get_matching_field_rule->($tag) // {
3749
            on_removed => {'action' => 'skip', 'rule' => 0},
3750
            on_appended => {'action' => 'skip', 'rule' => 0}
3751
        };
3752
        if ($tag < 10) {
3753
            # on_existing = on_match
3754
            if ($rule->{on_appended}->{action} eq 'skip') { # TODO: replace with "protect", "keep"
3755
                push @merged_record_fields, @{$current_fields->{$tag}};
3756
            }
3757
            elsif ($rule->{on_appended}->{action} eq 'append') {
3758
                push @merged_record_fields, @{$incoming_fields->{$tag}};
3759
            }
3760
            if (
3761
                $rule->{on_appended}->{action} eq 'append' &&
3762
                $rule->{on_removed}->{action} eq 'skip'
3763
            ) {
3764
                #TODO: This is an invalid combination for control fields, warn!!
3765
                # Or should perform client/server side validation to prevent this choice
3766
            }
3767
        }
3768
        else {
3769
            # Compute intersection and diff using field data
3770
            my %current_fields_by_data = map { $hash_field_data->($_) => $_ } @{$current_fields->{$tag}};
3771
            my %incoming_fields_by_data = map { $hash_field_data->($_) => $_ } @{$incoming_fields->{$tag}};
3772
            my ($current_fields_only, $common_fields, $incoming_fields_only) = $diff_by_key->(\%current_fields_by_data, \%incoming_fields_by_data);
3773
3774
            # First add common fields (intersection)
3775
            # Unchanged
3776
            if (@{$common_fields}) {
3777
                push @merged_record_fields, @{$common_fields};
3778
            }
3779
            # Removed
3780
            if (@{$current_fields_only}) {
3781
                if ($rule->{on_removed}->{action} eq 'skip') {
3782
                    push @merged_record_fields, @{$current_fields_only};
3783
                }
3784
            }
3785
            # Appended
3786
            if (@{$incoming_fields_only}) {
3787
                if ($rule->{on_appended}->{action} eq 'append') {
3788
                    push @merged_record_fields, @{$incoming_fields_only};
3789
                }
3790
            }
3791
        }
3792
    }
3793
    if ($#merged_record_fields != 0) {
3794
        $merged_record->insert_fields_ordered(@merged_record_fields);
3795
    }
3796
    return $merged_record;
3797
}
3798
3799
=head2 GetMarcPermissions
3800
3801
    my $marc_permissions = GetMarcPermissions()
3802
3803
Loads MARC field permissions from the marc_permissions table.
3804
3805
Returns:
3806
3807
=over 4
3808
3809
=item C<$marc_permissions>
3810
3811
hashref with permissions structure for use with GetMarcPermissionsAction.
3812
3813
=back
3814
3815
=cut
3816
3817
sub GetMarcPermissions {
3818
    my ($filter) = @_;
3819
    my $dbh = C4::Context->dbh;
3820
    my $rule_count = 0;
3821
    my $modules = GetMarcPermissionsModules();
3822
    # We only care about modules included in the context/filter
3823
    # TODO: Perhaps make sure source => '*' is default?
3824
    my @filter_modules = grep { exists $filter->{$_->{name}} } @{$modules};
3825
3826
    my $cache = Koha::Caches->get_instance();
3827
    my $permissions = $cache->get_from_cache('marc_permissions', { unsafe => 1 });
3828
3829
    if (!$permissions) {
3830
        my $query = '
3831
            SELECT `marc_permissions`.*,
3832
                `marc_permissions_modules`.`name`,
3833
                `marc_permissions_modules`.`description`,
3834
                `marc_permissions_modules`.`specificity`
3835
                FROM `marc_permissions`
3836
                LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
3837
                ORDER BY `marc_permissions_modules`.`specificity`, `id`
3838
        ';
3839
        my $sth = $dbh->prepare($query);
3840
        $sth->execute();
3841
        while (my $perm = $sth->fetchrow_hashref) {
3842
            my $target = ($permissions->{$perm->{name}}->{$perm->{filter}}->{$perm->{tagfield}} //= {});
3843
            foreach my $event (GetMarcPermissionEvents()) {
3844
                $target->{$event} = { action => $perm->{$event}, rule => $perm->{'id'} };
3845
            }
3846
        }
3847
        $cache->set_in_cache('marc_permissions', $permissions);
3848
    }
3849
3850
    my $filtered_permissions = undef;
3851
    foreach my $module (@filter_modules) {
3852
        if (
3853
            exists $permissions->{$module->{name}} &&
3854
            exists $permissions->{$module->{name}}->{$filter->{$module->{name}}}
3855
        ) {
3856
            # TODO: Support multiple overlapping filters/context??
3857
            $filtered_permissions = $permissions->{$module->{name}}->{$filter->{$module->{name}}};
3858
            last;
3859
        }
3860
    }
3861
    if (!$filtered_permissions) {
3862
        # No perms matching specific context conditions found, try wildcard value for each active context
3863
        foreach my $module (@filter_modules) {
3864
            if (exists $permissions->{$module->{name}}->{'*'}) {
3865
                $filtered_permissions = $permissions->{$module->{name}}->{'*'};
3866
                last;
3867
            }
3868
        }
3869
    }
3870
    return $filtered_permissions;
3871
}
3872
3873
# TODO: Use this in GetMarcPermissions
3874
=head2 GetMarcPermissionsRules
3875
3876
    my $rules = GetMarcPermissionsRules()
3877
3878
Returns:
3879
3880
=over 4
3881
3882
=item C<$rules>
3883
3884
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
3885
table in order of module specificity and rule id.
3886
3887
=back
3888
3889
=cut
3890
3891
sub GetMarcPermissionsRules {
3892
    my $dbh = C4::Context->dbh;
3893
    my @rules = ();
3894
3895
    my $query = '
3896
    SELECT `marc_permissions`.`id`,
3897
           `marc_permissions`.`tagfield`,
3898
           `marc_permissions`.`filter`,
3899
           `marc_permissions`.`on_new`,
3900
           `marc_permissions`.`on_appended`,
3901
           `marc_permissions`.`on_removed`,
3902
           `marc_permissions`.`on_deleted`,
3903
           `marc_permissions_modules`.`name` as  `module`,
3904
           `marc_permissions_modules`.`description`,
3905
           `marc_permissions_modules`.`specificity`
3906
    FROM `marc_permissions`
3907
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
3908
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
3909
    ';
3910
    my $sth = $dbh->prepare($query);
3911
    $sth->execute();
3912
    while ( my $row = $sth->fetchrow_hashref ) {
3913
        push(@rules, $row);
3914
    }
3915
3916
    return wantarray ? @rules : \@rules;
3917
}
3918
3919
=head2 GetMarcPermissionsModules
3920
3921
    my $modules = GetMarcPermissionsModules()
3922
3923
Returns:
3924
3925
=over 4
3926
3927
=item C<$modules>
3928
3929
array (in list context, arrayref otherwise) of hashrefs from
3930
marc_permissions_modules table in order of specificity.
3931
3932
=back
3933
3934
=cut
3537
3935
3936
sub GetMarcPermissionsModules {
3937
    my $dbh = C4::Context->dbh;
3938
    my @modules = ();
3939
3940
    my $query = '
3941
    SELECT *
3942
    FROM `marc_permissions_modules`
3943
    ORDER BY `specificity` DESC
3944
    ';
3945
    my $sth = $dbh->prepare($query);
3946
    $sth->execute();
3947
    while ( my $row = $sth->fetchrow_hashref ) {
3948
        push(@modules, $row);
3949
    }
3950
3951
    return wantarray ? @modules : \@modules;
3952
}
3953
3954
sub GetMarcPermissionEvents {
3955
    return ('on_new', 'on_appended', 'on_removed', 'on_deleted');
3956
}
3957
3958
=head2 ModMarcPermissionsRule
3959
3960
    my $success = ModMarcPermissionsRule($id, $fields)
3961
3962
Modifies rule in the marc_permissions table.
3963
3964
=over 4
3965
3966
=item C<$id>
3967
3968
rule id to modify
3969
3970
=item C<$fields>
3971
3972
hashref defining the table fields
3973
3974
      * tagfield - required
3975
      * module - required
3976
      * filter - required
3977
      * on_new - required
3978
      * on_appended - required
3979
      * on_removed - required
3980
      * on_deleted - required
3981
3982
=back
3983
3984
Returns:
3985
3986
=over 4
3987
3988
=item C<$success>
3989
3990
undef if an error occurs, otherwise true.
3991
3992
=back
3993
3994
=cut
3995
3996
sub ModMarcPermissionsRule {
3997
    my ($id, $f) = @_;
3998
    my $dbh = C4::Context->dbh;
3999
4000
    my $query = '
4001
    UPDATE `marc_permissions`
4002
    SET
4003
      tagfield = ?,
4004
      module = ?,
4005
      filter = ?,
4006
      on_new = ?,
4007
      on_appended = ?,
4008
      on_removed = ?,
4009
      on_deleted = ?
4010
    WHERE
4011
      id = ?
4012
    ';
4013
    my $sth = $dbh->prepare($query);
4014
    my $result = $sth->execute (
4015
        $f->{tagfield},
4016
        $f->{module},
4017
        $f->{filter},
4018
        $f->{on_new},
4019
        $f->{on_appended},
4020
        $f->{on_removed},
4021
        $f->{on_deleted},
4022
        $id
4023
    );
4024
    ClearMarcPermissionsRulesCache();
4025
    return $result;
4026
}
4027
4028
sub ClearMarcPermissionsRulesCache {
4029
    my $cache = Koha::Caches->get_instance();
4030
    $cache->clear_from_cache('marc_permissions');
4031
}
4032
4033
=head2 AddMarcPermissionsRule
4034
4035
    my $success = AddMarcPermissionsRule($fields)
4036
4037
Add rule to the marc_permissions table.
4038
4039
=over 4
4040
4041
=item C<$fields>
4042
4043
hashref defining the table fields
4044
4045
      tagfield - required
4046
      module - required
4047
      filter - required
4048
      on_new - required
4049
      on_appended - required
4050
      on_removed - required
4051
      on_deleted - required
4052
4053
=back
4054
4055
Returns:
4056
4057
=over 4
4058
4059
=item C<$success>
4060
4061
undef if an error occurs, otherwise true.
4062
4063
=back
4064
4065
=cut
4066
4067
sub AddMarcPermissionsRule {
4068
    my $f = shift;
4069
    my $dbh = C4::Context->dbh;
4070
    my $query = '
4071
    INSERT INTO `marc_permissions`
4072
    (
4073
      tagfield,
4074
      module,
4075
      filter,
4076
      on_new,
4077
      on_appended,
4078
      on_removed,
4079
      on_deleted
4080
    )
4081
    VALUES (?, ?, ?, ?, ?, ?, ?)
4082
    ';
4083
    my $sth = $dbh->prepare($query);
4084
    my $result = $sth->execute (
4085
        $f->{tagfield},
4086
        $f->{module},
4087
        $f->{filter},
4088
        $f->{on_new},
4089
        $f->{on_appended},
4090
        $f->{on_removed},
4091
        $f->{on_deleted}
4092
    );
4093
    ClearMarcPermissionsRulesCache();
4094
    return $result;
4095
}
4096
4097
=head2 DelMarcPermissionsRule
4098
4099
    my $success = DelMarcPermissionsRule($id)
4100
4101
Deletes rule from the marc_permissions table.
4102
4103
=over 4
4104
4105
=item C<$id>
4106
4107
rule id to delete
4108
4109
=back
4110
4111
Returns:
4112
4113
=over 4
4114
4115
=item C<$success>
4116
4117
undef if an error occurs, otherwise true.
4118
4119
=back
4120
4121
=cut
4122
4123
sub DelMarcPermissionsRule {
4124
    my $id = shift;
4125
    my $dbh = C4::Context->dbh;
4126
    my $query = '
4127
    DELETE FROM `marc_permissions`
4128
    WHERE
4129
      id = ?
4130
    ';
4131
    my $sth = $dbh->prepare($query);
4132
    my $result = $sth->execute($id);
4133
    ClearMarcPermissionsRulesCache();
4134
    return $result;
4135
}
4136
1;
3538
4137
3539
__END__
4138
__END__
3540
4139
(-)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/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 840-846 if ( $op eq "addbiblio" ) { Link Here
840
            BiblioAutoLink( $record, $frameworkcode );
841
            BiblioAutoLink( $record, $frameworkcode );
841
        } 
842
        } 
842
        if ( $is_a_modif ) {
843
        if ( $is_a_modif ) {
843
            ModBiblio( $record, $biblionumber, $frameworkcode );
844
            my $member = Koha::Patrons->find($loggedinuser);
845
            ModBiblio( $record, $biblionumber, $frameworkcode, {
846
                    context => {
847
                        source => $z3950 ? 'z39.50' : 'intranet',
848
                        category => $member->{'category_type'},
849
                        borrower => $loggedinuser
850
                    }
851
                }
852
            );
844
        }
853
        }
845
        else {
854
        else {
846
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
855
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
Lines 933-938 elsif ( $op eq "delete" ) { Link Here
933
    $template->param(
942
    $template->param(
934
        biblionumberdata => $biblionumber,
943
        biblionumberdata => $biblionumber,
935
        op               => $op,
944
        op               => $op,
945
        z3950            => $z3950
936
    );
946
    );
937
    if ( $op eq "duplicate" ) {
947
    if ( $op eq "duplicate" ) {
938
        $biblionumber = "";
948
        $biblionumber = "";
(-)a/help.pl (+5 lines)
Lines 23-28 use C4::Output; Link Here
23
# use C4::Auth;
23
# use C4::Auth;
24
use C4::Context;
24
use C4::Context;
25
use CGI qw ( -utf8 );
25
use CGI qw ( -utf8 );
26
use C4::Biblio;
26
27
27
sub _help_template_file_of_url {
28
sub _help_template_file_of_url {
28
    my $url = shift;
29
    my $url = shift;
Lines 74-77 if ( $help_version =~ m|^(\d+)\.(\d{2}).*$| ) { Link Here
74
}
75
}
75
$template->param( helpVersion => $help_version );
76
$template->param( helpVersion => $help_version );
76
77
78
my $rules = GetMarcPermissionsRules();
79
my $modules = GetMarcPermissionsModules();
80
$template->param( rules => $rules, modules => $modules );
81
77
output_html_with_http_headers $query, "", $template->output;
82
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
<h5>Acquisition parameters</h5>
46
<h5>Acquisition parameters</h5>
(-)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
                <h3>Acquisition parameters</h3>
100
                <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 (+7 lines)
Lines 276-278 Cataloging: Link Here
276
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
276
            - "All values of repeating tags and subfields will be printed with the given RIS tag."
277
            - "<br/>"
277
            - "<br/>"
278
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
278
            - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing."
279
        -
280
            - When importing records
281
            - pref: MARCPermissions
282
              choices:
283
                  yes: "use"
284
                  no: "don't use"
285
            - 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 562-567 function Changefwk() { Link Here
562
[% END %]
562
[% END %]
563
        <input type="hidden" name="op" value="addbiblio" />
563
        <input type="hidden" name="op" value="addbiblio" />
564
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode %]" />
564
        <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode %]" />
565
        <input type="hidden" name="z3950" value="[% z3950 %]" />
565
        <input type="hidden" name="biblionumber" value="[% biblionumber %]" />
566
        <input type="hidden" name="biblionumber" value="[% biblionumber %]" />
566
        <input type="hidden" name="breedingid" value="[% breedingid %]" />
567
        <input type="hidden" name="breedingid" value="[% breedingid %]" />
567
        <input type="hidden" name="changed_framework" value="" />
568
        <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 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);
(-)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 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