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 2544-2550 sub TransformHtmlToMarc { Link Here
2544
2563
2545
=head2 TransformMarcToKoha
2564
=head2 TransformMarcToKoha
2546
2565
2547
    $result = TransformMarcToKoha( $record, undef, $limit )
2566
    $result = TransformMarcToKoha($record, undef, $biblionumber, $option)
2548
2567
2549
Extract data from a MARC bib record into a hashref representing
2568
Extract data from a MARC bib record into a hashref representing
2550
Koha biblio, biblioitems, and items fields.
2569
Koha biblio, biblioitems, and items fields.
Lines 2555-2561 hash_ref. Link Here
2555
=cut
2574
=cut
2556
2575
2557
sub TransformMarcToKoha {
2576
sub TransformMarcToKoha {
2558
    my ( $record, $frameworkcode, $limit_table ) = @_;
2577
    my ( $record, $frameworkcode, $limit_table, $biblionumber ) = @_;
2559
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2578
    # FIXME  Parameter $frameworkcode is obsolete and will be removed
2560
    $limit_table //= q{};
2579
    $limit_table //= q{};
2561
2580
Lines 3242-3248 sub _koha_delete_biblio_metadata { Link Here
3242
3261
3243
=head2 ModBiblioMarc
3262
=head2 ModBiblioMarc
3244
3263
3245
  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3264
  &ModBiblioMarc($newrec, $biblionumber, $frameworkcode, $options);
3246
3265
3247
Add MARC XML data for a biblio to koha
3266
Add MARC XML data for a biblio to koha
3248
3267
Lines 3569-3576 sub RemoveAllNsb { Link Here
3569
    return $record;
3588
    return $record;
3570
}
3589
}
3571
3590
3572
1;
3591
=head2 ApplyMarcPermissions
3592
3593
    my $record = ApplyMarcPermissions($arguments)
3594
3595
Applies marc permission rules to a record.
3596
3597
C<$arguments> is expected to be a hashref with below keys defined.
3598
3599
=over 4
3600
3601
=item C<biblionumber>
3602
biblionumber of old record
3603
3604
=item C<record>
3605
record that will modify old record
3606
3607
=item C<frameworkcode>
3608
only tags included in framework will be processed
3609
3610
=item C<filter>
3611
hashref containing at least one filter module from the marc_permissions_modules
3612
table in form {module => filter}. Three predefined filter modules exists:
3613
3614
    * source
3615
    * category
3616
    * borrower
3617
3618
=item C<log>
3619
optional reference to array that will be filled with rule evaluation log
3620
entries.
3621
3622
=item C<nolog>
3623
optional boolean which when true disables logging to action log.
3624
3625
=back
3626
3627
Returns:
3628
3629
=over 4
3630
3631
=item C<$record>
3632
3633
new MARC record based on C<record> with C<filter> applied. If no old
3634
record for C<biblionumber> can be found, C<record> is returned unchanged.
3635
Default action when no matching filter found is to leave old record unchanged.
3636
3637
=back
3638
3639
=cut
3640
3641
sub ApplyMarcPermissions {
3642
    my ($arguments) = @_;
3643
    my $biblionumber = $arguments->{biblionumber};
3644
    my $incoming_record = $arguments->{record};
3645
3646
    if ( !$biblionumber ) {
3647
        carp 'ApplyMarcPermissions called on undefined biblionumber';
3648
        return;
3649
    }
3650
    if ( !$incoming_record ) {
3651
        carp 'ApplyMarcPermissions called on undefined record';
3652
        return;
3653
    }
3654
    my $old_record = GetMarcBiblio({ biblionumber => $biblionumber });
3655
3656
    my $merge_rules = undef;
3657
    if ($old_record && $arguments->{filter} && ($merge_rules = GetMarcPermissions($arguments->{filter}))) {
3658
        return MergeRecords($old_record, $incoming_record, $merge_rules);
3659
    }
3660
    return $incoming_record;
3661
}
3662
3663
sub MergeRecords {
3664
    my ($old_record, $incoming_record, $merge_rules) = @_;
3665
    my $is_matching_regex = sub {
3666
        my ( $tag, $m ) = @_;
3667
3668
        # tag is not exactly same as possible regex
3669
        $tag ne $m &&
3670
3671
        # wildcard
3672
        $m ne '*' &&
3673
3674
        # valid tagDataType
3675
        $m !~ /^(0[1-9A-z][\dA-Z]) |
3676
        ([1-9A-z][\dA-z]{2})$/x &&
3677
3678
        # nor valid controltagDataType
3679
        $m !~ /00[1-9A-Za-z]{1}/ &&
3680
3681
        # so we try it as a regex
3682
        $tag =~ /^$m$/
3683
    };
3684
3685
    my $fields_by_tag = sub {
3686
        my ($record) = @_;
3687
        my $fields = {};
3688
        foreach my $field ($record->fields()) {
3689
            $fields->{$field->tag()} //= [];
3690
            push @{$fields->{$field->tag()}}, $field;
3691
        }
3692
        return $fields;
3693
    };
3694
3695
    my $hash_field_data = sub {
3696
        my ($field) = @_;
3697
        my $indicators = join("\x1E", map { $field->indicator($_) } (1, 2));
3698
        return $indicators . "\x1E" . join("\x1E", sort map { join "\x1E", @{$_} } $field->subfields());
3699
    };
3700
3701
    my $diff_by_key = sub {
3702
        my ($a, $b) = @_;
3703
        my @removed;
3704
        my @intersecting;
3705
        my @added;
3706
        my %keys_index = map { $_ => undef } (keys %{$a}, keys %{$b});
3707
        foreach my $key (keys %keys_index) {
3708
            if ($a->{$key} && $b->{$key}) {
3709
                push @intersecting, $a->{$key};
3710
            }
3711
            elsif ($a->{$key}) {
3712
                push @removed, $a->{$key};
3713
            }
3714
            else {
3715
                push @added, $b->{$key};
3716
            }
3717
        }
3718
        return (\@removed, \@intersecting, \@added);
3719
    };
3720
3721
    my $get_matching_field_rule = sub {
3722
        my ($tag) = @_;
3723
        my $matched_rule = undef;
3724
        # Exact match takes precedence
3725
        if (exists $merge_rules->{$tag}) {
3726
            $matched_rule = $merge_rules->{$tag};
3727
        }
3728
        else {
3729
            # TODO: sorty by module/weight rule id or something, grab first matching via listutils thingy
3730
            my @matching_rules = map { $merge_rules->{$_} } grep { $is_matching_regex->($tag, $_) } sort keys %{$merge_rules};
3731
            # TODO: fix
3732
            if (@matching_rules) {
3733
                $matched_rule = pop @matching_rules;
3734
            }
3735
            elsif($merge_rules->{'*'}) {
3736
                $matched_rule = $merge_rules->{'*'};
3737
            }
3738
        }
3739
        return $matched_rule;
3740
    };
3741
3742
    my $merged_record = MARC::Record->new();
3743
    my @merged_record_fields;
3744
3745
    # Leader is always overwritten, or kept???
3746
    $merged_record->leader($incoming_record->leader());
3747
3748
    my $current_fields = $fields_by_tag->($old_record);
3749
    my $incoming_fields = $fields_by_tag->($incoming_record);
3750
3751
    # First we get all new incoming control fields
3752
    my @new_field_tags = grep { !(exists $current_fields->{$_}) } keys %{$incoming_fields};
3753
3754
    foreach my $tag (@new_field_tags) {
3755
        my $rule = $get_matching_field_rule->($tag) // {
3756
            on_new => {'action' => 'skip', 'rule' => 0}
3757
        };
3758
        if (
3759
            $rule->{on_new}->{action} eq 'add' ||
3760
            $rule->{on_new}->{action} eq 'overwrite' # ???
3761
        ) { # Or could just be write/protect?
3762
            # Hmm, only one control field possible??
3763
            push @merged_record_fields, @{$incoming_fields->{$tag}};
3764
        }
3765
    }
3766
3767
    # Then we get all control fields no longer present in incoming fields
3768
    # (removed)
3769
    my @deleted_field_tags = grep { !(exists $incoming_fields->{$_}) } keys %{$current_fields};
3770
    foreach my $tag (@deleted_field_tags) {
3771
        my $rule = $get_matching_field_rule->($tag) // {
3772
            on_deleted => {'action' => 'skip', 'rule' => 0}
3773
        };
3774
        if ($rule->{on_deleted}->{action} eq 'skip') {
3775
            push @merged_record_fields, @{$current_fields->{$tag}};
3776
        }
3777
    }
3778
3779
    # Then we get the intersection of control fields, present both in
3780
    # current and incoming record (possibly to be overwritten)
3781
    my @common_field_tags = grep { exists $incoming_fields->{$_} } keys %{$current_fields};
3782
    foreach my $tag (@common_field_tags) {
3783
        # Is control field
3784
        my $rule = $get_matching_field_rule->($tag) // {
3785
            on_removed => {'action' => 'skip', 'rule' => 0},
3786
            on_appended => {'action' => 'skip', 'rule' => 0}
3787
        };
3788
        if ($tag < 10) {
3789
            # on_existing = on_match
3790
            if ($rule->{on_appended}->{action} eq 'skip') { # TODO: replace with "protect", "keep"
3791
                push @merged_record_fields, @{$current_fields->{$tag}};
3792
            }
3793
            elsif ($rule->{on_appended}->{action} eq 'append') {
3794
                push @merged_record_fields, @{$incoming_fields->{$tag}};
3795
            }
3796
            if (
3797
                $rule->{on_appended}->{action} eq 'append' &&
3798
                $rule->{on_removed}->{action} eq 'skip'
3799
            ) {
3800
                #TODO: This is an invalid combination for control fields, warn!!
3801
                # Or should perform client/server side validation to prevent this choice
3802
            }
3803
        }
3804
        else {
3805
            # Compute intersection and diff using field data
3806
            my %current_fields_by_data = map { $hash_field_data->($_) => $_ } @{$current_fields->{$tag}};
3807
            my %incoming_fields_by_data = map { $hash_field_data->($_) => $_ } @{$incoming_fields->{$tag}};
3808
            my ($current_fields_only, $common_fields, $incoming_fields_only) = $diff_by_key->(\%current_fields_by_data, \%incoming_fields_by_data);
3809
3810
            # First add common fields (intersection)
3811
            # Unchanged
3812
            if (@{$common_fields}) {
3813
                push @merged_record_fields, @{$common_fields};
3814
            }
3815
            # Removed
3816
            if (@{$current_fields_only}) {
3817
                if ($rule->{on_removed}->{action} eq 'skip') {
3818
                    push @merged_record_fields, @{$current_fields_only};
3819
                }
3820
            }
3821
            # Appended
3822
            if (@{$incoming_fields_only}) {
3823
                if ($rule->{on_appended}->{action} eq 'append') {
3824
                    push @merged_record_fields, @{$incoming_fields_only};
3825
                }
3826
            }
3827
        }
3828
    }
3829
    if ($#merged_record_fields != 0) {
3830
        $merged_record->insert_fields_ordered(@merged_record_fields);
3831
    }
3832
    return $merged_record;
3833
}
3834
3835
=head2 GetMarcPermissions
3836
3837
    my $marc_permissions = GetMarcPermissions()
3838
3839
Loads MARC field permissions from the marc_permissions table.
3840
3841
Returns:
3842
3843
=over 4
3844
3845
=item C<$marc_permissions>
3846
3847
hashref with permissions structure for use with GetMarcPermissionsAction.
3848
3849
=back
3850
3851
=cut
3852
3853
sub GetMarcPermissions {
3854
    my ($filter) = @_;
3855
    my $dbh = C4::Context->dbh;
3856
    my $rule_count = 0;
3857
    my $modules = GetMarcPermissionsModules();
3858
    # We only care about modules included in the context/filter
3859
    # TODO: Perhaps make sure source => '*' is default?
3860
    my @filter_modules = grep { exists $filter->{$_->{name}} } @{$modules};
3861
3862
    my $cache = Koha::Caches->get_instance();
3863
    my $permissions = $cache->get_from_cache('marc_permissions', { unsafe => 1 });
3864
3865
    if (!$permissions) {
3866
        my $query = '
3867
            SELECT `marc_permissions`.*,
3868
                `marc_permissions_modules`.`name`,
3869
                `marc_permissions_modules`.`description`,
3870
                `marc_permissions_modules`.`specificity`
3871
                FROM `marc_permissions`
3872
                LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
3873
                ORDER BY `marc_permissions_modules`.`specificity`, `id`
3874
        ';
3875
        my $sth = $dbh->prepare($query);
3876
        $sth->execute();
3877
        while (my $perm = $sth->fetchrow_hashref) {
3878
            my $target = ($permissions->{$perm->{name}}->{$perm->{filter}}->{$perm->{tagfield}} //= {});
3879
            foreach my $event (GetMarcPermissionEvents()) {
3880
                $target->{$event} = { action => $perm->{$event}, rule => $perm->{'id'} };
3881
            }
3882
        }
3883
        $cache->set_in_cache('marc_permissions', $permissions);
3884
    }
3885
3886
    my $filtered_permissions = undef;
3887
    foreach my $module (@filter_modules) {
3888
        if (
3889
            exists $permissions->{$module->{name}} &&
3890
            exists $permissions->{$module->{name}}->{$filter->{$module->{name}}}
3891
        ) {
3892
            # TODO: Support multiple overlapping filters/context??
3893
            $filtered_permissions = $permissions->{$module->{name}}->{$filter->{$module->{name}}};
3894
            last;
3895
        }
3896
    }
3897
    if (!$filtered_permissions) {
3898
        # No perms matching specific context conditions found, try wildcard value for each active context
3899
        foreach my $module (@filter_modules) {
3900
            if (exists $permissions->{$module->{name}}->{'*'}) {
3901
                $filtered_permissions = $permissions->{$module->{name}}->{'*'};
3902
                last;
3903
            }
3904
        }
3905
    }
3906
    return $filtered_permissions;
3907
}
3908
3909
# TODO: Use this in GetMarcPermissions
3910
=head2 GetMarcPermissionsRules
3911
3912
    my $rules = GetMarcPermissionsRules()
3913
3914
Returns:
3915
3916
=over 4
3917
3918
=item C<$rules>
3919
3920
array (in list context, arrayref otherwise) of hashrefs from marc_permissions
3921
table in order of module specificity and rule id.
3922
3923
=back
3924
3925
=cut
3926
3927
sub GetMarcPermissionsRules {
3928
    my $dbh = C4::Context->dbh;
3929
    my @rules = ();
3930
3931
    my $query = '
3932
    SELECT `marc_permissions`.`id`,
3933
           `marc_permissions`.`tagfield`,
3934
           `marc_permissions`.`filter`,
3935
           `marc_permissions`.`on_new`,
3936
           `marc_permissions`.`on_appended`,
3937
           `marc_permissions`.`on_removed`,
3938
           `marc_permissions`.`on_deleted`,
3939
           `marc_permissions_modules`.`name` as  `module`,
3940
           `marc_permissions_modules`.`description`,
3941
           `marc_permissions_modules`.`specificity`
3942
    FROM `marc_permissions`
3943
    LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id`
3944
    ORDER BY `marc_permissions_modules`.`specificity`, `id`
3945
    ';
3946
    my $sth = $dbh->prepare($query);
3947
    $sth->execute();
3948
    while ( my $row = $sth->fetchrow_hashref ) {
3949
        push(@rules, $row);
3950
    }
3951
3952
    return wantarray ? @rules : \@rules;
3953
}
3954
3955
=head2 GetMarcPermissionsModules
3956
3957
    my $modules = GetMarcPermissionsModules()
3958
3959
Returns:
3960
3961
=over 4
3962
3963
=item C<$modules>
3964
3965
array (in list context, arrayref otherwise) of hashrefs from
3966
marc_permissions_modules table in order of specificity.
3967
3968
=back
3969
3970
=cut
3573
3971
3972
sub GetMarcPermissionsModules {
3973
    my $dbh = C4::Context->dbh;
3974
    my @modules = ();
3975
3976
    my $query = '
3977
    SELECT *
3978
    FROM `marc_permissions_modules`
3979
    ORDER BY `specificity` DESC
3980
    ';
3981
    my $sth = $dbh->prepare($query);
3982
    $sth->execute();
3983
    while ( my $row = $sth->fetchrow_hashref ) {
3984
        push(@modules, $row);
3985
    }
3986
3987
    return wantarray ? @modules : \@modules;
3988
}
3989
3990
sub GetMarcPermissionEvents {
3991
    return ('on_new', 'on_appended', 'on_removed', 'on_deleted');
3992
}
3993
3994
=head2 ModMarcPermissionsRule
3995
3996
    my $success = ModMarcPermissionsRule($id, $fields)
3997
3998
Modifies rule in the marc_permissions table.
3999
4000
=over 4
4001
4002
=item C<$id>
4003
4004
rule id to modify
4005
4006
=item C<$fields>
4007
4008
hashref defining the table fields
4009
4010
      * tagfield - required
4011
      * module - required
4012
      * filter - required
4013
      * on_new - required
4014
      * on_appended - required
4015
      * on_removed - required
4016
      * on_deleted - required
4017
4018
=back
4019
4020
Returns:
4021
4022
=over 4
4023
4024
=item C<$success>
4025
4026
undef if an error occurs, otherwise true.
4027
4028
=back
4029
4030
=cut
4031
4032
sub ModMarcPermissionsRule {
4033
    my ($id, $f) = @_;
4034
    my $dbh = C4::Context->dbh;
4035
4036
    my $query = '
4037
    UPDATE `marc_permissions`
4038
    SET
4039
      tagfield = ?,
4040
      module = ?,
4041
      filter = ?,
4042
      on_new = ?,
4043
      on_appended = ?,
4044
      on_removed = ?,
4045
      on_deleted = ?
4046
    WHERE
4047
      id = ?
4048
    ';
4049
    my $sth = $dbh->prepare($query);
4050
    my $result = $sth->execute (
4051
        $f->{tagfield},
4052
        $f->{module},
4053
        $f->{filter},
4054
        $f->{on_new},
4055
        $f->{on_appended},
4056
        $f->{on_removed},
4057
        $f->{on_deleted},
4058
        $id
4059
    );
4060
    ClearMarcPermissionsRulesCache();
4061
    return $result;
4062
}
4063
4064
sub ClearMarcPermissionsRulesCache {
4065
    my $cache = Koha::Caches->get_instance();
4066
    $cache->clear_from_cache('marc_permissions');
4067
}
4068
4069
=head2 AddMarcPermissionsRule
4070
4071
    my $success = AddMarcPermissionsRule($fields)
4072
4073
Add rule to the marc_permissions table.
4074
4075
=over 4
4076
4077
=item C<$fields>
4078
4079
hashref defining the table fields
4080
4081
      tagfield - required
4082
      module - required
4083
      filter - required
4084
      on_new - required
4085
      on_appended - required
4086
      on_removed - required
4087
      on_deleted - required
4088
4089
=back
4090
4091
Returns:
4092
4093
=over 4
4094
4095
=item C<$success>
4096
4097
undef if an error occurs, otherwise true.
4098
4099
=back
4100
4101
=cut
4102
4103
sub AddMarcPermissionsRule {
4104
    my $f = shift;
4105
    my $dbh = C4::Context->dbh;
4106
    my $query = '
4107
    INSERT INTO `marc_permissions`
4108
    (
4109
      tagfield,
4110
      module,
4111
      filter,
4112
      on_new,
4113
      on_appended,
4114
      on_removed,
4115
      on_deleted
4116
    )
4117
    VALUES (?, ?, ?, ?, ?, ?, ?)
4118
    ';
4119
    my $sth = $dbh->prepare($query);
4120
    my $result = $sth->execute (
4121
        $f->{tagfield},
4122
        $f->{module},
4123
        $f->{filter},
4124
        $f->{on_new},
4125
        $f->{on_appended},
4126
        $f->{on_removed},
4127
        $f->{on_deleted}
4128
    );
4129
    ClearMarcPermissionsRulesCache();
4130
    return $result;
4131
}
4132
4133
=head2 DelMarcPermissionsRule
4134
4135
    my $success = DelMarcPermissionsRule($id)
4136
4137
Deletes rule from the marc_permissions table.
4138
4139
=over 4
4140
4141
=item C<$id>
4142
4143
rule id to delete
4144
4145
=back
4146
4147
Returns:
4148
4149
=over 4
4150
4151
=item C<$success>
4152
4153
undef if an error occurs, otherwise true.
4154
4155
=back
4156
4157
=cut
4158
4159
sub DelMarcPermissionsRule {
4160
    my $id = shift;
4161
    my $dbh = C4::Context->dbh;
4162
    my $query = '
4163
    DELETE FROM `marc_permissions`
4164
    WHERE
4165
      id = ?
4166
    ';
4167
    my $sth = $dbh->prepare($query);
4168
    my $result = $sth->execute($id);
4169
    ClearMarcPermissionsRulesCache();
4170
    return $result;
4171
}
4172
1;
3574
4173
3575
__END__
4174
__END__
3576
4175
(-)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 835-841 if ( $op eq "addbiblio" ) { Link Here
835
            BiblioAutoLink( $record, $frameworkcode );
836
            BiblioAutoLink( $record, $frameworkcode );
836
        } 
837
        } 
837
        if ( $is_a_modif ) {
838
        if ( $is_a_modif ) {
838
            ModBiblio( $record, $biblionumber, $frameworkcode );
839
            my $member = Koha::Patrons->find($loggedinuser);
840
            ModBiblio( $record, $biblionumber, $frameworkcode, {
841
                    context => {
842
                        source => $z3950 ? 'z39.50' : 'intranet',
843
                        category => $member->{'category_type'},
844
                        borrower => $loggedinuser
845
                    }
846
                }
847
            );
839
        }
848
        }
840
        else {
849
        else {
841
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
850
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
Lines 928-933 elsif ( $op eq "delete" ) { Link Here
928
    $template->param(
937
    $template->param(
929
        biblionumberdata => $biblionumber,
938
        biblionumberdata => $biblionumber,
930
        op               => $op,
939
        op               => $op,
940
        z3950            => $z3950
931
    );
941
    );
932
    if ( $op eq "duplicate" ) {
942
    if ( $op eq "duplicate" ) {
933
        $biblionumber = "";
943
        $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
[% 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 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 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