Bugzilla – Attachment 59811 Details for
Bug 14957
Write protecting MARC fields based on source of import
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Rule based permission for MARC fields
koha_bug_14957.patch (text/plain), 87.02 KB, created by
David Gustafsson
on 2017-02-02 12:44:21 UTC
(
hide
)
Description:
Rule based permission for MARC fields
Filename:
MIME Type:
Creator:
David Gustafsson
Created:
2017-02-02 12:44:21 UTC
Size:
87.02 KB
patch
obsolete
>diff --git a/C4/Biblio.pm b/C4/Biblio.pm >index 6d0c9bb..939602f 100644 >--- a/C4/Biblio.pm >+++ b/C4/Biblio.pm >@@ -106,6 +106,12 @@ BEGIN { > &CountItemsIssued > &CountBiblioInOrders > &GetSubscriptionsId >+ >+ &GetMarcPermissionsRules >+ &GetMarcPermissionsModules >+ &ModMarcPermissionsRule >+ &AddMarcPermissionsRule >+ &DelMarcPermissionsRule > ); > > # To modify something >@@ -133,6 +139,7 @@ BEGIN { > # they are useful in a few circumstances, so they are exported, > # but don't use them unless you are a core developer ;-) > push @EXPORT, qw( >+ &ApplyMarcPermissions > &ModBiblioMarc > ); > >@@ -257,7 +264,7 @@ sub AddBiblio { > _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode ); > > # now add the record >- ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save; >+ ModBiblioMarc( $record, $biblionumber, $frameworkcode, $options->{filter} ) unless $defer_marc_save; > > # update OAI-PMH sets > if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) { >@@ -270,7 +277,7 @@ sub AddBiblio { > > =head2 ModBiblio > >- ModBiblio( $record,$biblionumber,$frameworkcode); >+ ModBiblio($record, $biblionumber, $frameworkcode, $filter); > > Replace an existing bib record identified by C<$biblionumber> > with one supplied by the MARC::Record object C<$record>. The embedded >@@ -291,7 +298,8 @@ Returns 1 on success 0 on failure > =cut > > sub ModBiblio { >- my ( $record, $biblionumber, $frameworkcode ) = @_; >+ my ( $record, $biblionumber, $frameworkcode, $filter ) = @_; >+ > if (!$record) { > carp 'No record passed to ModBiblio'; > return 0; >@@ -329,13 +337,13 @@ sub ModBiblio { > _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber ); > > # load the koha-table data object >- my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode ); >+ my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode, undef, $biblionumber, $filter ); > > # update MARC subfield that stores biblioitems.cn_sort > _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode ); > > # update the MARC record (that now contains biblio and items) with the new record data >- &ModBiblioMarc( $record, $biblionumber, $frameworkcode ); >+ ModBiblioMarc( $record, $biblionumber, $frameworkcode, $filter ); > > # modify the other koha tables > _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode ); >@@ -2655,7 +2663,7 @@ sub TransformHtmlToMarc { > > =head2 TransformMarcToKoha > >- $result = TransformMarcToKoha( $record, $frameworkcode ) >+ $result = TransformMarcToKoha( $record, $frameworkcode, $biblionumber, $filter ) > > Extract data from a MARC bib record into a hashref representing > Koha biblio, biblioitems, and items fields. >@@ -2666,7 +2674,7 @@ hash_ref > =cut > > sub TransformMarcToKoha { >- my ( $record, $frameworkcode, $limit_table ) = @_; >+ my ( $record, $frameworkcode, $limit_table, $biblionumber, $filter ) = @_; > > my $result = {}; > if (!defined $record) { >@@ -2687,6 +2695,17 @@ sub TransformMarcToKoha { > $tables{'biblioitems'} = 1; > } > >+ # apply permissions >+ if ( C4::Context->preference('MARCPermissions') && $biblionumber && $filter) { >+ $record = ApplyMarcPermissions({ >+ biblionumber => $biblionumber, >+ record => $record, >+ frameworkcode => $frameworkcode, >+ filter => $filter, >+ nolog => 1 >+ }); >+ } >+ > # traverse through record > MARCFIELD: foreach my $field ( $record->fields() ) { > my $tag = $field->tag(); >@@ -3441,7 +3460,7 @@ sub _koha_delete_biblioitems { > > =head2 ModBiblioMarc > >- &ModBiblioMarc($newrec,$biblionumber,$frameworkcode); >+ &ModBiblioMarc($newrec,$biblionumber,$frameworkcode,$filter); > > Add MARC XML data for a biblio to koha > >@@ -3452,7 +3471,8 @@ Function exported, but should NOT be used, unless you really know what you're do > sub ModBiblioMarc { > # pass the MARC::Record to this function, and it will create the records in > # the marcxml field >- my ( $record, $biblionumber, $frameworkcode ) = @_; >+ my ( $record, $biblionumber, $frameworkcode, $filter ) = @_; >+ > if ( !$record ) { > carp 'ModBiblioMarc passed an undefined record'; > return; >@@ -3465,6 +3485,17 @@ sub ModBiblioMarc { > if ( !$frameworkcode ) { > $frameworkcode = ""; > } >+ >+ # apply permissions >+ if ( C4::Context->preference('MARCPermissions') ) { >+ $record = ApplyMarcPermissions({ >+ biblionumber => $biblionumber, >+ record => $record, >+ frameworkcode => $frameworkcode, >+ filter => $filter >+ }); >+ } >+ > my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?"); > $sth->execute( $frameworkcode, $biblionumber ); > $sth->finish; >@@ -3785,6 +3816,934 @@ sub RemoveAllNsb { > return $record; > } > >+=head2 ApplyMarcPermissions >+ >+ my $record = ApplyMarcPermissions($arguments) >+ >+Applies marc permission rules to a record. >+ >+C<$arguments> is expected to be a hashref with below keys defined. >+ >+=over 4 >+ >+=item C<biblionumber> >+biblionumber of old record >+ >+=item C<record> >+record that will modify old record >+ >+=item C<frameworkcode> >+only tags included in framework will be processed >+ >+=item C<filter> >+hashref containing at least one filter module from the marc_permissions_modules >+table in form {module => filter}. Three predefined filter modules exists: >+ >+ * source >+ * category >+ * borrower >+ >+=item C<log> >+optional reference to array that will be filled with rule evaluation log >+entries. >+ >+=item C<nolog> >+optional boolean which when true disables logging to action log. >+ >+=back >+ >+Returns: >+ >+=over 4 >+ >+=item C<$record> >+ >+new MARC record based on C<record> with C<filter> applied. If no old >+record for C<biblionumber> can be found, C<record> is returned unchanged. >+Default action when no matching filter found is to leave old record unchanged. >+ >+=back >+ >+=cut >+ >+sub ApplyMarcPermissions { >+ my $arguments = shift; >+ my $biblionumber = $arguments->{biblionumber}; >+ my $new_record = $arguments->{record}; >+ my $frameworkcode = $arguments->{frameworkcode} || ''; >+ >+ if ( !$biblionumber ) { >+ carp 'ApplyMarcPermissions called on undefined biblionumber'; >+ return; >+ } >+ if ( !$new_record ) { >+ carp 'ApplyMarcPermissions called on undefined record'; >+ return; >+ } >+ my $filter = $arguments->{filter} >+ or return $new_record; >+ >+ my $log = $arguments->{log} || []; >+ my $nolog = $arguments->{nolog}; >+ >+ my $dbh = C4::Context->dbh; >+ >+ my $is_regex = sub { >+ my ( $tag, $m ) = @_; >+ # tag is not exactly same as possible regex >+ $tag ne $m && >+ >+ # wildcard >+ $m ne '*' && >+ >+ # valid tagDataType >+ $m !~ /^(0[1-9A-z][\dA-Z]) | >+ ([1-9A-z][\dA-z]{2})$/x && >+ >+ # nor valid controltagDataType >+ $m !~ /00[1-9A-Za-z]{1}/ && >+ >+ # so we try it as a regex >+ $tag =~ /^$m$/ >+ }; >+ >+ my $old_record = GetMarcBiblio($biblionumber); >+ if ( $old_record ) { >+ my $ret_record = MARC::Record->new(); >+ my $perm = GetMarcPermissions() or return $new_record; >+ my $marc_struct = GetMarcStructure(1, $frameworkcode); >+ my $similarity_pref = C4::Context->preference("MARCPermissionsCorrectionSimilarity"); >+ $similarity_pref = defined $similarity_pref && >+ $similarity_pref >= 0 && >+ $similarity_pref <= 100 ? >+ $similarity_pref/100.0 : undef; >+ >+ # First came the leader ... >+ $ret_record->leader($old_record->leader()); >+ >+ # ... and then came all the tags in the framework >+ for my $tag (sort keys %{$marc_struct}) { >+ my @old_fields = $old_record->field($tag); >+ my @new_fields = $new_record->field($tag); >+ next unless @old_fields or @new_fields; >+ my @perms = ( >+ $perm->{'*'}, >+ ( # regex tags >+ map { $perm->{$_} } >+ grep { $is_regex->( $tag, $_ ) } sort keys %{$perm} >+ ), >+ $perm->{$tag} >+ ); >+ >+ # There can be more than one field for each tag, so we have to >+ # iterate all fields and distinguish which are new, removed or >+ # modified >+ my $max_fields = ($#old_fields, $#new_fields) >+ [$#old_fields< $#new_fields]; >+ for ( my $i_field = 0; $i_field <= $max_fields; $i_field++ ) { >+ my $old_field = $old_fields[$i_field]; >+ my $new_field = $new_fields[$i_field]; >+ >+ # Existing field >+ if ( $old_field and $new_field ) { >+ # Control fields >+ if ( $old_field->is_control_field() ) { >+ # Existing control field >+ if ( $old_field and $new_field ) { >+ my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @perms); >+ if ( defined $on_existing->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_existing->{rule}, >+ event => "existing", >+ action => $on_existing->{action}, >+ tag => $tag, >+ subfieldcode => undef, >+ filter => $filter >+ }; >+ if ( $on_existing->{action} eq 'skip' ) { >+ $ret_record->insert_fields_ordered($old_field); >+ next; >+ } elsif ( $on_existing->{action} eq 'overwrite' ) { >+ $ret_record->insert_fields_ordered($new_field); >+ next; >+ } elsif ( $on_existing->{action} eq 'add' ) { >+ $ret_record->insert_fields_ordered($old_field); >+ $ret_record->insert_fields_ordered($new_field); >+ next; >+ } elsif ( $on_existing->{action} eq 'add_or_correct' ) { >+ if ( $similarity_pref ) { >+ my $similarity = similarity $old_field->data(), $new_field->data(); >+ if ( $similarity >= $similarity_pref ) { >+ $ret_record->insert_fields_ordered($new_field); >+ } else { >+ $ret_record->insert_fields_ordered($old_field); >+ $ret_record->insert_fields_ordered($new_field); >+ } >+ } else { >+ $ret_record->insert_fields_ordered($old_field); >+ } >+ } >+ } else { # default to skip >+ $ret_record->insert_fields_ordered($old_field); >+ next; >+ } >+ # New control field >+ } elsif ( not $old_field and $new_field ) { >+ my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms); >+ if ( defined $on_new->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_new->{rule}, >+ event => "new", >+ action => $on_new->{action}, >+ tag => $tag, >+ subfieldcode => undef, >+ filter => $filter >+ }; >+ if ( $on_new->{action} eq 'skip' ) { # Redundant >+ next; >+ } elsif ( $on_new->{action} eq 'add' ) { >+ $ret_record->insert_fields_ordered($new_field); >+ next; >+ } >+ } >+ # Removed control field >+ } elsif ( $old_field and not $new_field ) { >+ my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms); >+ if ( defined $on_removed->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_removed->{rule}, >+ event => "removed", >+ action => $on_removed->{action}, >+ tag => $tag, >+ subfieldcode => undef >+ }; >+ if ( $on_removed->{action} eq 'skip' ) { >+ $ret_record->insert_fields_ordered($old_field); >+ next; >+ } elsif ( $on_removed->{action} eq 'remove' ) { >+ next; >+ } >+ } else { # default to skip >+ $ret_record->insert_fields_ordered($old_field); >+ next; >+ } >+ } >+ # Indicators and subfields >+ } else { >+ # Indicators >+ my @old_ind = map { $old_field->indicator($_) } (1,2); >+ my @new_ind = map { $new_field->indicator($_) } (1,2); >+ my @ind = ('',''); >+ >+ for my $i ((0,1)) { >+ my @ind_perms = ( >+ @perms, >+ $perm->{$tag}->{subfields}->{ 'i' . ( $i + 1 ) } >+ ); >+ >+ # Existing indicator >+ if ( defined $old_ind[$i] and defined $new_ind[$i] ) { >+ my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @ind_perms); >+ if ( defined $on_existing->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_existing->{rule}, >+ event => "existing", >+ action => $on_existing->{action}, >+ tag => $tag, >+ subfieldcode => 'i' . ( $i + 1 ), >+ filter => $filter >+ }; >+ if ( $on_existing->{action} eq 'skip' ) { >+ $ind[$i] = $old_ind[$i]; >+ } elsif ( $on_existing->{action} eq 'overwrite' ) { >+ $ind[$i] = $new_ind[$i]; >+ } >+ } else { # default to skip >+ $ind[$i] = $old_ind[$i]; >+ } >+ # New indicator >+ } elsif ( not defined $old_ind[$i] and defined $new_ind[$i] ) { >+ my $on_new = GetMarcPermissionsAction('on_new', $filter, @ind_perms); >+ if ( defined $on_new->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_new->{rule}, >+ event => "new", >+ action => $on_new->{action}, >+ tag => $tag, >+ subfieldcode => 'i' . ( $i + 1 ), >+ filter => $filter >+ }; >+ if ( $on_new->{action} eq 'skip' ) { >+ $ind[$i] = $old_ind[$i]; >+ } elsif ( $on_new->{action} eq 'add' ) { >+ $ind[$i] = $new_ind[$i]; >+ } >+ } >+ # Removed indicator >+ } elsif ( defined $old_ind[$i] and not defined $new_ind[$i] ) { >+ my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @ind_perms); >+ if ( defined $on_removed->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_removed->{rule}, >+ event => "removed", >+ action => $on_removed->{action}, >+ tag => $tag, >+ subfieldcode => 'i' . ( $i + 1 ), >+ filter => $filter >+ }; >+ if ( $on_removed->{action} eq 'skip' ) { >+ $ind[$i] = $old_ind[$i]; >+ } elsif ( $on_removed->{action} eq 'remove' ) { >+ $ind[$i] = ''; >+ } >+ } else { # default to skip >+ $ind[$i] = $old_ind[$i]; >+ } >+ } >+ } >+ >+ # Subfields >+ my @subfields = (); >+ my %proccessed_subcodes = (); >+ >+ # Try to preserve subfield order by first processing >+ # subfields from the old record, and then subfields from >+ # the framework structure unless include in old record. >+ my @sorted_subfields = ( >+ map { shift @{$_} } $old_field->subfields(), >+ () = sort keys %{ $marc_struct->{$tag} } >+ ); >+ for my $subcode ( @sorted_subfields ) { >+ next if $proccessed_subcodes{$subcode}; >+ $proccessed_subcodes{$subcode} = 1; >+ next unless ref $marc_struct->{$tag}{$subcode} eq 'HASH'; >+ my @old_subfields = $old_field->subfield($subcode); >+ my @new_subfields = $new_field->subfield($subcode); >+ next unless @old_subfields or @new_subfields; >+ >+ my @subfield_perms = ( >+ @perms, >+ $perm->{'*'}->{subfields}->{'*'}, >+ $perm->{$tag}->{subfields}->{'*'}, >+ ( # tag->regex >+ map { $perm->{$tag}->{subfields}->{$_} } >+ grep { >+ # subcode is not exactly same as >+ # possible regex >+ $subcode ne $_ && >+ >+ # wildcard, not checking for valid >+ # subfieldcodeDataType since it >+ # contains too many regex characters >+ $_ !~ /^(\*)$/ && >+ >+ # so we try it as a regex >+ $subcode =~ /$_/ >+ } sort >+ keys %{ $perm->{$tag}->{subfields} } >+ ), >+ ( # regex->* >+ map { $perm->{$_}->{subfields}->{'*'} } >+ grep { $is_regex->( $tag, $_ ) } >+ sort keys %{$perm} >+ ), >+ $perm->{'*'}->{subfields}->{$subcode}, >+ ( # regex->subcode >+ map { $perm->{$_}->{subfields}->{$subcode} } >+ grep { $is_regex->( $tag, $_ ) } >+ sort keys %{$perm} >+ ), >+ $perm->{$tag}->{subfields}->{$subcode} >+ ); >+ >+ # Existing subfield >+ if ( @old_subfields and @new_subfields ) { >+ my $on_existing = GetMarcPermissionsAction('on_existing', $filter, @subfield_perms); >+ if ( defined $on_existing->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_existing->{rule}, >+ event => "existing", >+ action => $on_existing->{action}, >+ tag => $tag, >+ subfieldcode => $subcode, >+ filter => $filter >+ }; >+ if ( $on_existing->{action} eq 'skip' ) { >+ push(@subfields, map {($subcode,$_)} @old_subfields); >+ next; >+ } elsif ( $on_existing->{action} eq 'overwrite' ) { >+ push(@subfields, map {($subcode,$_)} @new_subfields); >+ next; >+ } elsif ( $on_existing->{action} eq 'add' ) { >+ push(@subfields, map {($subcode,$_)} @old_subfields); >+ push(@subfields, map {($subcode,$_)} @new_subfields); >+ next; >+ } elsif ( $on_existing->{action} eq 'add_or_correct' ) { >+ if ( $similarity_pref ) { >+ my @corrected_vals = (); >+ >+ # correct all old subfields >+ for ( my $i_new = 0; $i_new <= $#new_subfields; $i_new++ ) { >+ my $new_val = $new_subfields[$i_new]; >+ for ( my $i_old = 0; $i_old <= $#old_subfields; $i_old++ ) { >+ my $old_val = $old_subfields[$i_old]; >+ next if not defined $old_val or not defined $new_val; >+ my $similarity = similarity ($old_val, $new_val); >+ if ( $similarity >= $similarity_pref ) { >+ $corrected_vals[$i_old] = $new_val; >+ $new_subfields[$i_new] = undef; >+ $old_subfields[$i_old] = undef; >+ last; >+ } >+ } >+ } >+ >+ # insert all unchanged old subfields >+ map { >+ $corrected_vals[$_] = $old_subfields[$_] >+ if defined $old_subfields[$_] >+ } 0 .. $#old_subfields; >+ >+ >+ # append all remaning new subfields >+ map {push @corrected_vals, $_ if defined} @new_subfields; >+ >+ push(@subfields, map {($subcode,$_)} @corrected_vals); >+ } else { >+ push(@subfields, map {($subcode,$_)} @old_subfields); >+ } >+ } >+ } else { # default to skip >+ push(@subfields, map {($subcode,$_)} @old_subfields); >+ next; >+ } >+ # New subfield >+ } elsif ( not @old_subfields and @new_subfields ) { >+ my $on_new = GetMarcPermissionsAction('on_new', $filter, @subfield_perms); >+ if ( defined $on_new->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_new->{rule}, >+ event => "new", >+ action => $on_new->{action}, >+ tag => $tag, >+ subfieldcode => $subcode, >+ filter => $filter >+ }; >+ if ( $on_new->{action} eq 'skip' ) { >+ next; >+ } elsif ( $on_new->{action} eq 'add' ) { >+ push(@subfields, map {($subcode,$_)} @new_subfields); >+ next; >+ } >+ } >+ # Removed subfield >+ } elsif ( @old_subfields and not @new_subfields ) { >+ my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @subfield_perms); >+ if ( defined $on_removed->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_removed->{rule}, >+ event => "removed", >+ action => $on_removed->{action}, >+ tag => $tag, >+ subfieldcode => $subcode, >+ filter => $filter >+ }; >+ if ( $on_removed->{action} eq 'skip' ) { >+ push(@subfields, ($subcode => @old_subfields)); >+ next; >+ } elsif ( $on_removed->{action} eq 'remove' ) { >+ next; >+ } >+ } else { # default to skip >+ push(@subfields, ($subcode => @old_subfields)); >+ next; >+ } >+ } >+ } # / for each subfield >+ $ret_record->insert_grouped_field(MARC::Field->new($tag, $ind[0], $ind[1], @subfields)); >+ } >+ # New field >+ } elsif ( not $old_field and $new_field ) { >+ my $on_new = GetMarcPermissionsAction('on_new', $filter, @perms); >+ >+ if ( defined $on_new->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_new->{rule}, >+ event => "new", >+ action => $on_new->{action}, >+ tag => $tag, >+ subfieldcode => undef, >+ filter => $filter >+ }; >+ if ( $on_new->{action} eq 'skip' ) { >+ next; >+ } elsif ( $on_new->{action} eq 'add' ) { >+ $ret_record->insert_fields_ordered($new_field); >+ next; >+ } >+ } >+ # Removed field >+ } elsif ( $old_field and not $new_field ) { >+ my $on_removed = GetMarcPermissionsAction('on_removed', $filter, @perms); >+ if ( defined $on_removed->{action} ) { >+ push @{$log}, >+ { >+ rule => $on_removed->{rule}, >+ event => "removed", >+ action => $on_removed->{action}, >+ tag => $tag, >+ subfieldcode => undef, >+ filter => $filter >+ }; >+ if ( $on_removed->{action} eq 'skip' ) { >+ $ret_record->insert_grouped_field($old_field); >+ next; >+ } elsif ( $on_removed->{action} eq 'remove' ) { >+ next; >+ } >+ } else { # default to skip >+ $ret_record->insert_grouped_field($old_field); >+ next; >+ } >+ } >+ } # / for each field >+ } # / for each tag >+ if ( !$nolog && C4::Context->preference("MARCPermissionsLog") ) { >+ my $log_str = ''; >+ my $n = $#{$log}; >+ for my $l ( @{$log} ) { >+ $log_str .= "{\n"; >+ my $kn = keys %{$l}; >+ for my $k ( sort keys %{$l} ) { >+ if ( $k eq 'filter' ) { >+ $log_str .= " filter => {\n"; >+ my $fkn = keys %{ $l->{$k} }; >+ for my $fk ( sort keys %{ $l->{$k} } ) { >+ $log_str .= ' ' >+ . $fk . ' => ' >+ . $l->{$k}->{$fk} >+ . ( --$fkn ? ',' : '' ) . "\n"; >+ } >+ $log_str .= " }" . ( --$kn ? ',' : '' ) . "\n"; >+ } >+ else { >+ $log_str .= ' ' >+ . $k . ' => ' >+ . $l->{$k} >+ . ( --$kn ? ',' : '' ) . "\n" >+ if defined $l->{$k}; >+ } >+ } >+ $log_str .= '}' . ( $n-- ? ",\n" : '' ); >+ } >+ logaction( "CATALOGUING", "MODIFY", $biblionumber, $log_str ); >+ } >+ return $ret_record; >+ } >+ return $new_record; >+} >+ >+ >+=head2 GetMarcPermissions >+ >+ my $marc_permissions = GetMarcPermissions() >+ >+Loads MARC field permissions from the marc_permissions table. >+ >+Returns: >+ >+=over 4 >+ >+=item C<$marc_permissions> >+ >+hashref with permissions structure for use with GetMarcPermissionsAction. >+ >+=back >+ >+=cut >+ >+sub GetMarcPermissions { >+ my $dbh = C4::Context->dbh; >+ my $rule_count = 0; >+ my %perms = (); >+ >+ my $query = ' >+ SELECT `marc_permissions`.*, >+ `marc_permissions_modules`.`name`, >+ `marc_permissions_modules`.`description`, >+ `marc_permissions_modules`.`specificity` >+ FROM `marc_permissions` >+ LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id` >+ ORDER BY `marc_permissions_modules`.`specificity`, `id` >+ '; >+ my $sth = $dbh->prepare($query); >+ $sth->execute(); >+ while ( my $row = $sth->fetchrow_hashref ) { >+ $rule_count++; >+ if ( defined $row->{tagsubfield} and $row->{tagsubfield} ) { >+ $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} } >+ ->{on_existing}->{ $row->{name} }->{ $row->{filter} } = >+ { action => $row->{on_existing}, rule => $row->{'id'} }; >+ $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} } >+ ->{on_new}->{ $row->{name} }->{ $row->{filter} } = >+ { action => $row->{on_new}, rule => $row->{'id'} }; >+ $perms{ $row->{tagfield} }->{subfields}->{ $row->{tagsubfield} } >+ ->{on_removed}->{ $row->{name} }->{ $row->{filter} } = >+ { action => $row->{on_removed}, rule => $row->{'id'} }; >+ } >+ else { >+ $perms{ $row->{tagfield} }->{on_existing}->{ $row->{name} } >+ ->{ $row->{filter} } = >+ { action => $row->{on_existing}, rule => $row->{'id'} }; >+ $perms{ $row->{tagfield} }->{on_new}->{ $row->{name} } >+ ->{ $row->{filter} } = >+ { action => $row->{on_new}, rule => $row->{'id'} }; >+ $perms{ $row->{tagfield} }->{on_removed}->{ $row->{name} } >+ ->{ $row->{filter} } = >+ { action => $row->{on_removed}, rule => $row->{'id'} }; >+ } >+ } >+ >+ return unless $rule_count; >+ return \%perms; >+} >+ >+=head2 GetMarcPermissionsAction >+ >+ my $action = GetMarcPermissionsAction($event, $filter, @permissions) >+ >+Gets action based on C<$event>, C<$filter> and C<@permissions>. >+ >+=over 4 >+ >+=item C<$event> >+ >+which event: 'on_existing', 'on_new' or 'on_removed' >+ >+=item C<$filter> >+ >+hashref containing at least one filter module from the marc_permissions_modules >+table in form {module => filter}. Three predefined filter modules exists: >+ >+ * source >+ * category >+ * borrower >+ >+=item C<@permissions> >+ >+list of permission structures in order of specificity from least to most >+specific. >+ >+=back >+ >+Returns: >+ >+=over 4 >+ >+=item C<$action> >+ >+hashref defining matching action. >+ >+=back >+ >+=cut >+ >+sub GetMarcPermissionsAction { >+ my $what = shift or return; >+ my $filter = shift or return; >+ my @perms = @_; >+ my $modules = GetMarcPermissionsModules(); >+ my $action = undef; >+ >+ for my $perm (@perms) { >+ next if not defined $perm; >+ next if not defined $perm->{$what}; >+ my $tmp_action = undef; >+ # TODO: Borde kunna loopa over $perm->what? >+ # 1. Sortera filterargument efter prio (intersect keys med modules) >+ # Intersect med modules? >+ # foreach $filter as (keys %{$filter}) { >+ # } >+ for my $module ( @{$modules} ) { >+ my $action_candidate = defined $perm->{$what}->{ $module->{'name'} } ? $perm->{$what}->{ $module->{'name'} } : undef; >+ >+ # Problem: permission-modul med hogre specificitet overridarar aven om specifik >+ # matchning pa annan modul (med lagre) >+ if ($action_candidate) { >+ if ( >+ defined $filter->{ $module->{'name'} } and >+ defined $action_candidate->{ $filter->{ $module->{'name'} } }->{action} >+ ) { >+ $tmp_action = $perm->{$what}->{ $module->{'name'} }->{ $filter->{ $module->{'name'} } }; >+ last; >+ } >+ elsif (defined $action_candidate->{'*'}->{action} and !$tmp_action) { >+ $tmp_action = $perm->{$what}->{ $module->{'name'} }->{'*'}; >+ } >+ } >+ } >+ $action = $tmp_action if defined $tmp_action; >+ } >+ >+ return $action; >+} >+ >+=head2 GetMarcPermissionsRules >+ >+ my $rules = GetMarcPermissionsRules() >+ >+Returns: >+ >+=over 4 >+ >+=item C<$rules> >+ >+array (in list context, arrayref otherwise) of hashrefs from marc_permissions >+table in order of module specificity and rule id. >+ >+=back >+ >+=cut >+ >+sub GetMarcPermissionsRules { >+ my $dbh = C4::Context->dbh; >+ my @rules = (); >+ >+ my $query = ' >+ SELECT `marc_permissions`.`id`, >+ `marc_permissions`.`tagfield`, >+ `marc_permissions`.`tagsubfield`, >+ `marc_permissions`.`filter`, >+ `marc_permissions`.`on_existing`, >+ `marc_permissions`.`on_new`, >+ `marc_permissions`.`on_removed`, >+ `marc_permissions_modules`.`name` as `module`, >+ `marc_permissions_modules`.`description`, >+ `marc_permissions_modules`.`specificity` >+ FROM `marc_permissions` >+ LEFT JOIN `marc_permissions_modules` ON `module` = `marc_permissions_modules`.`id` >+ ORDER BY `marc_permissions_modules`.`specificity`, `id` >+ '; >+ my $sth = $dbh->prepare($query); >+ $sth->execute(); >+ while ( my $row = $sth->fetchrow_hashref ) { >+ push(@rules, $row); >+ } >+ >+ return wantarray ? @rules : \@rules; >+} >+ >+=head2 GetMarcPermissionsModules >+ >+ my $modules = GetMarcPermissionsModules() >+ >+Returns: >+ >+=over 4 >+ >+=item C<$modules> >+ >+array (in list context, arrayref otherwise) of hashrefs from >+marc_permissions_modules table in order of specificity. >+ >+=back >+ >+=cut >+ >+sub GetMarcPermissionsModules { >+ my $dbh = C4::Context->dbh; >+ my @modules = (); >+ >+ my $query = ' >+ SELECT * >+ FROM `marc_permissions_modules` >+ ORDER BY `specificity` DESC >+ '; >+ my $sth = $dbh->prepare($query); >+ $sth->execute(); >+ while ( my $row = $sth->fetchrow_hashref ) { >+ push(@modules, $row); >+ } >+ >+ return wantarray ? @modules : \@modules; >+} >+ >+=head2 ModMarcPermissionsRule >+ >+ my $success = ModMarcPermissionsRule($id, $fields) >+ >+Modifies rule in the marc_permissions table. >+ >+=over 4 >+ >+=item C<$id> >+ >+rule id to modify >+ >+=item C<$fields> >+ >+hashref defining the table fields >+ >+ * tagfield - required >+ * tagsubfield >+ * module - required >+ * filter - required >+ * on_existing - required >+ * on_new - required >+ * on_removed - required >+ >+=back >+ >+Returns: >+ >+=over 4 >+ >+=item C<$success> >+ >+undef if an error occurs, otherwise true. >+ >+=back >+ >+=cut >+ >+sub ModMarcPermissionsRule { >+ my ($id, $f) = @_; >+ my $dbh = C4::Context->dbh; >+ my $query = ' >+ UPDATE `marc_permissions` >+ SET >+ tagfield = ?, >+ tagsubfield = ?, >+ module = ?, >+ filter = ?, >+ on_existing = ?, >+ on_new = ?, >+ on_removed = ? >+ WHERE >+ id = ? >+ '; >+ my $sth = $dbh->prepare($query); >+ return $sth->execute ( >+ $f->{tagfield}, >+ $f->{tagsubfield}, >+ $f->{module}, >+ $f->{filter}, >+ $f->{on_existing}, >+ $f->{on_new}, >+ $f->{on_removed}, >+ $id >+ ); >+} >+ >+=head2 AddMarcPermissionsRule >+ >+ my $success = AddMarcPermissionsRule($fields) >+ >+Add rule to the marc_permissions table. >+ >+=over 4 >+ >+=item C<$fields> >+ >+hashref defining the table fields >+ >+ tagfield - required >+ tagsubfield >+ module - required >+ filter - required >+ on_existing - required >+ on_new - required >+ on_removed - required >+ >+=back >+ >+Returns: >+ >+=over 4 >+ >+=item C<$success> >+ >+undef if an error occurs, otherwise true. >+ >+=back >+ >+=cut >+ >+sub AddMarcPermissionsRule { >+ my $f = shift; >+ my $dbh = C4::Context->dbh; >+ my $query = ' >+ INSERT INTO `marc_permissions` >+ ( >+ tagfield, >+ tagsubfield, >+ module, >+ filter, >+ on_existing, >+ on_new, >+ on_removed >+ ) >+ VALUES (?, ?, ?, ?, ?, ?, ?) >+ '; >+ my $sth = $dbh->prepare($query); >+ return $sth->execute ( >+ $f->{tagfield}, >+ $f->{tagsubfield}, >+ $f->{module}, >+ $f->{filter}, >+ $f->{on_existing}, >+ $f->{on_new}, >+ $f->{on_removed} >+ ); >+} >+ >+=head2 DelMarcPermissionsRule >+ >+ my $success = DelMarcPermissionsRule($id) >+ >+Deletes rule from the marc_permissions table. >+ >+=over 4 >+ >+=item C<$id> >+ >+rule id to delete >+ >+=back >+ >+Returns: >+ >+=over 4 >+ >+=item C<$success> >+ >+undef if an error occurs, otherwise true. >+ >+=back >+ >+=cut >+ >+sub DelMarcPermissionsRule { >+ my $id = shift; >+ my $dbh = C4::Context->dbh; >+ my $query = ' >+ DELETE FROM `marc_permissions` >+ WHERE >+ id = ? >+ '; >+ my $sth = $dbh->prepare($query); >+ return $sth->execute($id); >+} > 1; > > >diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm >index 4e1f75f..b5342f2 100644 >--- a/C4/Installer/PerlDependencies.pm >+++ b/C4/Installer/PerlDependencies.pm >@@ -852,6 +852,11 @@ our $PERL_DEPS = { > required => 1, > min_ver => '0.28', > }, >+ 'String::Similarity' => { >+ usage => 'cataloguing', >+ required => 1, >+ min_version => '1.04', >+ }, > }; > > 1; >diff --git a/C4/Items.pm b/C4/Items.pm >index 3330a1f..1122136 100644 >--- a/C4/Items.pm >+++ b/C4/Items.pm >@@ -406,6 +406,8 @@ sub AddItemBatchFromMarc { > logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); > > my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields); >+ >+ # @FIXME: why modify clone record here since not even saving below? > $item_field->replace_with($new_item_marc->field($itemtag)); > } > >diff --git a/admin/marc-permissions.pl b/admin/marc-permissions.pl >new file mode 100755 >index 0000000..03999cb >--- /dev/null >+++ b/admin/marc-permissions.pl >@@ -0,0 +1,123 @@ >+#!/usr/bin/perl >+ >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use strict; >+use warnings; >+ >+# standard or CPAN modules used >+use CGI qw ( -utf8 ); >+use CGI::Cookie; >+use MARC::File::USMARC; >+ >+# Koha modules used >+use C4::Context; >+use C4::Koha; >+use C4::Auth; >+use C4::AuthoritiesMarc; >+use C4::Output; >+use C4::Biblio; >+use C4::ImportBatch; >+use C4::Matcher; >+use C4::BackgroundJob; >+use C4::Labels::Batch; >+ >+my $script_name = "/cgi-bin/koha/admin/marc-permissions.pl"; >+ >+my $input = new CGI; >+my $op = $input->param('op') || ''; >+ >+my ( $template, $loggedinuser, $cookie ) = get_template_and_user( >+ { >+ template_name => "admin/marc-permissions.tt", >+ query => $input, >+ type => "intranet", >+ authnotrequired => 0, >+ flagsrequired => { parameters => 'parameters_remaining_permissions' }, >+ debug => 1, >+ } >+); >+ >+my %cookies = parse CGI::Cookie($cookie); >+our $sessionID = $cookies{'CGISESSID'}->value; >+ >+my $rules; >+if ( $op eq "remove" ) { >+ $template->{VARS}->{removeConfirm} = 1; >+ my @removeIDs = $input->multi_param('batchremove'); >+ push( @removeIDs, scalar $input->param('id') ) if $input->param('id'); >+ >+ $rules = GetMarcPermissionsRules(); >+ for my $removeID (@removeIDs) { >+ map { $_->{'remove'} = 1 if $_->{'id'} == $removeID } @{$rules}; >+ } >+ >+} >+elsif ( $op eq "doremove" ) { >+ my @removeIDs = $input->multi_param('batchremove'); >+ push( @removeIDs, scalar $input->param('id') ) if $input->param('id'); >+ for my $removeID (@removeIDs) { >+ DelMarcPermissionsRule($removeID); >+ } >+ >+ $rules = GetMarcPermissionsRules(); >+ >+} >+elsif ( $op eq "edit" ) { >+ $template->{VARS}->{edit} = 1; >+ my $id = $input->param('id'); >+ $rules = GetMarcPermissionsRules(); >+ map { $_->{'edit'} = 1 if $_->{'id'} == $id } @{$rules}; >+ >+} >+elsif ( $op eq "doedit" ) { >+ my $id = $input->param('id'); >+ my $fields = { >+ module => scalar $input->param('module'), >+ tagfield => scalar $input->param('tagfield'), >+ tagsubfield => scalar $input->param('tagsubfield'), >+ filter => scalar $input->param('filter'), >+ on_existing => scalar $input->param('on_existing'), >+ on_new => scalar $input->param('on_new'), >+ on_removed => scalar $input->param('on_removed') >+ }; >+ >+ ModMarcPermissionsRule( $id, $fields ); >+ $rules = GetMarcPermissionsRules(); >+ >+} >+elsif ( $op eq "add" ) { >+ my $fields = { >+ module => scalar $input->param('module'), >+ tagfield => scalar $input->param('tagfield'), >+ tagsubfield => scalar $input->param('tagsubfield'), >+ filter => scalar $input->param('filter'), >+ on_existing => scalar $input->param('on_existing'), >+ on_new => scalar $input->param('on_new'), >+ on_removed => scalar $input->param('on_removed') >+ }; >+ >+ AddMarcPermissionsRule($fields); >+ $rules = GetMarcPermissionsRules(); >+ >+} >+else { >+ $rules = GetMarcPermissionsRules(); >+} >+my $modules = GetMarcPermissionsModules(); >+$template->param( rules => $rules, modules => $modules ); >+ >+output_html_with_http_headers $input, $cookie, $template->output; >diff --git a/cataloguing/addbiblio.pl b/cataloguing/addbiblio.pl >index 1929d0c..fe28bff 100755 >--- a/cataloguing/addbiblio.pl >+++ b/cataloguing/addbiblio.pl >@@ -837,7 +837,14 @@ if ( $op eq "addbiblio" ) { > BiblioAutoLink( $record, $frameworkcode ); > } > if ( $is_a_modif ) { >- ModBiblio( $record, $biblionumber, $frameworkcode ); >+ my ($member) = C4::Members::GetMember('borrowernumber' => $loggedinuser); >+ ModBiblio( $record, $biblionumber, $frameworkcode, >+ { >+ source => $z3950 ? 'z39.50' : 'intranet', >+ category => $member->{'category_type'}, >+ borrower => $loggedinuser >+ } >+ ); > } > else { > ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode ); >@@ -930,6 +937,7 @@ elsif ( $op eq "delete" ) { > $template->param( > biblionumberdata => $biblionumber, > op => $op, >+ z3950 => $z3950 > ); > if ( $op eq "duplicate" ) { > $biblionumber = ""; >diff --git a/help.pl b/help.pl >index ad86a2d..86855d0 100755 >--- a/help.pl >+++ b/help.pl >@@ -24,6 +24,7 @@ use C4::Output; > # use C4::Auth; > use C4::Context; > use CGI qw ( -utf8 ); >+use C4::Biblio; > > sub _help_template_file_of_url { > my $url = shift; >@@ -75,4 +76,8 @@ if ( $help_version =~ m|^(\d+)\.(\d{2}).*$| ) { > } > $template->param( helpVersion => $help_version ); > >+my $rules = GetMarcPermissionsRules(); >+my $modules = GetMarcPermissionsModules(); >+$template->param( rules => $rules, modules => $modules ); >+ > output_html_with_http_headers $query, "", $template->output; >diff --git a/installer/data/mysql/atomicupdate/bug_14957-marc-permissions-syspref.sql b/installer/data/mysql/atomicupdate/bug_14957-marc-permissions-syspref.sql >new file mode 100644 >index 0000000..d697fc3 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/bug_14957-marc-permissions-syspref.sql >@@ -0,0 +1,3 @@ >+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'); >+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'); >+INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES ('MARCPermissionsCorrectionSimilarity','90','','New field value is considered a correction if it is similar to the old to the specified percentage.','Integer'); >diff --git a/installer/data/mysql/atomicupdate/bug_14957-marc-permissions.sql b/installer/data/mysql/atomicupdate/bug_14957-marc-permissions.sql >new file mode 100644 >index 0000000..ffbc633 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/bug_14957-marc-permissions.sql >@@ -0,0 +1,31 @@ >+DROP TABLE IF EXISTS `marc_permissions`; >+DROP TABLE IF EXISTS `marc_permissions_modules`; >+ >+CREATE TABLE `marc_permissions_modules` ( >+ `id` int(11) NOT NULL auto_increment, >+ `name` varchar(24) NOT NULL, >+ `description` varchar(255), >+ `specificity` int(11) NOT NULL DEFAULT 0, -- higher specificity will override rules with lower specificity >+ PRIMARY KEY(`id`) >+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; >+ >+-- a couple of useful default filter modules >+-- these are used in various scripts, so don't remove them if you don't know >+-- what you're doing. >+-- New filter modules can be added here when needed >+INSERT INTO `marc_permissions_modules` VALUES(NULL, 'source', 'source from where modification request was sent', 0); >+INSERT INTO `marc_permissions_modules` VALUES(NULL, 'category', 'categorycode of user who requested modification', 1); >+INSERT INTO `marc_permissions_modules` VALUES(NULL, 'borrower', 'borrowernumber of user who requested modification', 2); >+ >+CREATE TABLE `marc_permissions` ( >+ `id` int(11) NOT NULL auto_increment, >+ `tagfield` varchar(255) NOT NULL, -- can be regexe, so need > 3 chars >+ `tagsubfield` varchar(255) DEFAULT NULL, -- can be regex, so need > 1 char >+ `module` int(11) NOT NULL, >+ `filter` varchar(255) NOT NULL, >+ `on_existing` ENUM('skip', 'overwrite', 'add', 'add_or_correct') DEFAULT NULL, >+ `on_new` ENUM('skip', 'add') DEFAULT NULL, >+ `on_removed` ENUM('skip', 'remove') DEFAULT NULL, >+ PRIMARY KEY(`id`), >+ CONSTRAINT `marc_permissions_ibfk1` FOREIGN KEY (`module`) REFERENCES `marc_permissions_modules` (`id`) ON DELETE CASCADE >+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >index 9f8f4f6..41bb194 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >@@ -50,6 +50,7 @@ > <li><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></li> > <li><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></li> > <li><a href="/cgi-bin/koha/admin/items_search_fields.pl">Item search fields</a></li> >+ <li><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></li> > </ul> > > <h5>Acquisition parameters</h5> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >index 6c128d6..af778dd 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >@@ -89,6 +89,8 @@ > <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration</a></dt> > <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd> > [% END %] >+ <dt><a href="/cgi-bin/koha/admin/marc-permissions.pl">MARC field permissions</a></dt> >+ <dd>Managed MARC field permissions</dd> > </dl> > > <h3>Acquisition parameters</h3> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-permissions.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-permissions.tt >new file mode 100644 >index 0000000..ff0670a >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-permissions.tt >@@ -0,0 +1,314 @@ >+[% USE Koha %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title>Koha › Administration › MARC field permissions</title> >+[% INCLUDE 'doc-head-close.inc' %] >+<link rel="stylesheet" type="text/css" href="[% interface %]/[% theme %]/css/datatables.css" /> >+[% INCLUDE 'datatables.inc' %] >+ >+<style type="text/css"> >+ .required { >+ background-color: #C00; >+ } >+</style> >+ >+<script type="text/javascript"> >+//<![CDATA[ >+function doSubmit(op, id) { >+ $('<input type="hidden"/>') >+ .attr('name', 'op') >+ .attr('value', op) >+ .appendTo('#marc-permissions-form'); >+ >+ if(id) { >+ $('<input type="hidden"/>') >+ .attr('name', 'id') >+ .attr('value', id) >+ .appendTo('#marc-permissions-form'); >+ } >+ >+ var valid = true; >+ if( op == 'add' || op == 'edit') { >+ var validate = [ >+ $('#marc-permissions-form input[name="filter"]'), >+ $('#marc-permissions-form input[name="tagfield"]') >+ ]; >+ for(var i=0; i < validate.length; i++) { >+ if(validate[i].val().length == 0) { >+ validate[i].addClass('required'); >+ valid = false; >+ } else { >+ validate[i].removeClass('required'); >+ } >+ } >+ } >+ >+ if(valid ) { >+ $('#marc-permissions-form').submit(); >+ } >+ >+ return valid; >+} >+ >+$(document).ready(function(){ >+ $('#doremove').on("click",function(){ >+ doSubmit('doremove'); >+ }); >+ $('#doedit').on("click",function(){ >+ doSubmit('doedit', $("#doedit").attr('value')); >+ }); >+ $('#add').on("click", function(){ >+ doSubmit('add'); >+ return false; >+ }); >+ $('#btn_batchremove').on("click", function(){ >+ doSubmit('remove'); >+ }); >+ >+ /* disable some options if subfield is indicator */ >+ $('input[name="tagsubfield"]').change(function() { >+ if( /i[0-9]/.test($(this).val()) ) { >+ $('select[name="on_existing"] option[value="add"]').attr('disabled', 'disabled') >+ $('select[name="on_existing"] option[value="add_or_correct"]').attr('disabled', 'disabled') >+ } else { >+ $('select[name="on_existing"] option[value="add"]').removeAttr('disabled') >+ $('select[name="on_existing"] option[value="add_or_correct"]').removeAttr('disabled') >+ } >+ }); >+ >+ /* disable batch remove unless one or more checkboxes are checked */ >+ $('input[name="batchremove"]').change(function() { >+ if($('input[name="batchremove"]:checked').length > 0) { >+ $('#btn_batchremove').removeAttr('disabled'); >+ } else { >+ $('#btn_batchremove').attr('disabled', 'disabled'); >+ } >+ }); >+ >+ /* check validity of regexes in field and subfield inputs */ >+ $('input[name="tagfield"], input[name="tagsubfield"]').change(function() { >+ $(this).removeClass('required'); >+ $(this).attr('title', ''); >+ if($(this).val() != '*') { >+ try { >+ new RegExp($(this).val()) >+ } catch ( e ) { >+ $(this).addClass('required'); >+ $(this).attr('title', 'Invalid regular expression: ' + e.message); >+ } >+ } >+ }); >+ >+ $.fn.dataTable.ext.order['dom-input'] = function (settings, col) { >+ return this.api().column(col, { order: 'index' }).nodes() >+ .map(function (td, i) { >+ if($('input', td).val() != undefined) { >+ return $('input', td).val(); >+ } else if($('select', td).val() != undefined) { >+ return $('option[selected="selected"]', td).val(); >+ } else { >+ return $(td).html(); >+ } >+ }); >+ } >+ >+ $('#marc-permissions').dataTable($.extend(true, {}, dataTablesDefaults, { >+ "aoColumns": [ >+ {"bSearchable": false, "bSortable": false}, >+ {"sSortDataType": "dom-input"}, >+ {"sSortDataType": "dom-input"}, >+ {"bSearchable": false, "sSortDataType": "dom-input"}, >+ {"bSearchable": false, "sSortDataType": "dom-input"}, >+ {"bSearchable": false, "sSortDataType": "dom-input"}, >+ {"bSearchable": false, "sSortDataType": "dom-input"}, >+ {"bSearchable": false, "sSortDataType": "dom-input"}, >+ {"bSearchable": false, "bSortable": false}, >+ {"bSearchable": false, "bSortable": false} >+ ], >+ "sPaginationType": "four_button" >+ })); >+ >+}); >+//]]> >+</script> >+</head> >+<body id="admin_marc-permissions" class="admin"> >+[% INCLUDE 'header.inc' %] >+[% INCLUDE 'cat-search.inc' %] >+ >+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> >+ › MARC field permissions >+</div> >+ >+<div id="doc3" class="yui-t2"> >+ <div id="bd"> >+ <div id="yui-main"> >+ <div class="yui-b"> >+ >+<h1>Manage MARC field permissions</h1> >+ >+[% UNLESS Koha.Preference( 'MARCPermissions' ) %] >+ <div class="dialog message"> >+ The <b>MARCPermissions</b> preference is not set, don't forget to enable it for rules to take effect. >+ </div> >+[% END %] >+[% IF removeConfirm %] >+<div class="dialog alert"> >+<h3>Remove rule?</h3> >+<p>Are you sure you want to remove the selected rule(s)?</p> >+ >+<form action="[% script_name %]" method="GET"> >+ <input type="submit" value="No, do not remove" class="deny"/> >+</form> >+<input type="button" value="Yes, remove" class="approve" id="doremove" /> >+</div> >+[% END %] >+ >+<form action="[% script_name %]" method="POST" id="marc-permissions-form"> >+<table id="marc-permissions"> >+ <thead><tr> >+ <th>Rule</th> >+ <th>Tag</th> >+ <th>Subfield tag</th> >+ <th>Module</th> >+ <th>Filter</th> >+ <th>On Existing</th> >+ <th>On New</th> >+ <th>On Removed</th> >+ <th>Actions</th> >+ <th> </th> >+ </tr></thead> >+ [% UNLESS edit %] >+ <tfoot> >+ <tr> >+ <th> </th> >+ <th><input type="text" size="5" name="tagfield"/></th> >+ <th><input type="text" size="5" name="tagsubfield"/></th> >+ <th> >+ <select name="module"> >+ [% FOREACH module IN modules %] >+ <option value="[% module.id %]">[% module.name %]</option> >+ [% END %] >+ </select> >+ </th> >+ <th><input type="text" size="5" name="filter"/></th> >+ <th> >+ <select name="on_existing"> >+ [% FOR on_ex IN ['skip', 'overwrite', 'add', 'add_or_correct'] %] >+ <option value="[% on_ex %]">[% on_ex %]</option> >+ [% END %] >+ </select> >+ </th> >+ <th> >+ <select name="on_new"> >+ [% FOR on_new IN ['skip', 'add'] %] >+ <option value="[% on_new %]">[% on_new %]</option> >+ [% END %] >+ </select> >+ </th> >+ <th> >+ <select name="on_removed"> >+ [% FOR on_removed IN ['skip', 'remove'] %] >+ <option value="[% on_removed %]">[% on_removed %]</option> >+ [% END %] >+ </select> >+ </th> >+ <th><button class="btn btn-small" title="Add" id="add"><i class="fa fa-plus"></i> Add rule</button></th> >+ <th><button id="btn_batchremove" disabled="disabled" class="btn btn-small" title="Batch remove"><i class="fa fa-trash"></i> Delete selected</button></th> >+ </tr> >+ </tfoot> >+ [% END %] >+ <tbody> >+ [% FOREACH rule IN rules %] >+ <tr id="[% rule.id %]"> >+ [% IF rule.edit %] >+ <td>[% rule.id %]</td> >+ <td><input type="text" size="3" name="tagfield" value="[% rule.tagfield %]"/></td> >+ <td><input type="text" size="1" name="tagsubfield" value="[% rule.tagsubfield %]"/></td> >+ <td> >+ <select name="module"> >+ [% FOREACH module IN modules %] >+ [% IF module.name == rule.module %] >+ <option value="[% module.id %]" selected="selected">[% module.name %]</option> >+ [% ELSE %] >+ <option value="[% module.id %]">[% module.name %]</option> >+ [% END %] >+ [% END %] >+ </select> >+ </td> >+ <td><input type="text" size="5" name="filter" value="[% rule.filter %]"/></td> >+ <td> >+ <select name="on_existing"> >+ [% FOR on_ex IN ['skip', 'overwrite', 'add', 'add_or_correct'] %] >+ [% IF on_ex == rule.on_existing %] >+ <option value="[% on_ex %]" selected="selected">[% on_ex %]</option> >+ [% ELSE %] >+ <option value="[% on_ex %]">[% on_ex %]</option> >+ [% END %] >+ [% END %] >+ </select> >+ </td> >+ <td> >+ <select name="on_new"> >+ [% FOR on_new IN ['skip', 'add'] %] >+ [% IF on_new == rule.on_new %] >+ <option value="[% on_new %]" selected="selected">[% on_new %]</option> >+ [% ELSE %] >+ <option value="[% on_new %]">[% on_new %]</option> >+ [% END %] >+ [% END %] >+ </select> >+ </td> >+ <td> >+ <select name="on_removed"> >+ [% FOR on_removed IN ['skip', 'remove'] %] >+ [% IF on_removed == rule.on_removed %] >+ <option value="[% on_removed %]" selected="selected">[% on_removed %]</option> >+ [% ELSE %] >+ <option value="[% on_removed %]">[% on_removed %]</option> >+ [% END %] >+ [% END %] >+ </select> >+ </td> >+ <td class="actions"> >+ <button class="btn btn-mini" title="Save" id="doedit" value="[% rule.id %]"><i class="fa fa-check"></i> Save</button> >+ <a href="?"><button class="btn btn-mini" title="Cancel" ><i class="fa fa-times"></i> Cancel</button></a> >+ </td> >+ <td></td> >+ [% ELSE %] >+ <td>[% rule.id %]</td> >+ <td>[% rule.tagfield %]</td> >+ <td>[% rule.tagsubfield%]</td> >+ <td>[% rule.module %]</td> >+ <td>[% rule.filter %]</td> >+ <td>[% rule.on_existing %]</td> >+ <td>[% rule.on_new %]</td> >+ <td>[% rule.on_removed %]</td> >+ <td class="actions"> >+ <a href="?op=remove&id=[% rule.id %]" title="Delete" class="btn btn-mini"><i class="fa fa-trash"></i> Delete</a> >+ <a href="?op=edit&id=[% rule.id %]" title="Edit" class="btn btn-mini"><i class="fa fa-pencil"></i> Edit</a> >+ </td> >+ <td> >+ [% IF rule.remove %] >+ <input type="checkbox" name="batchremove" value="[% rule.id %]" checked="checked"/> >+ [% ELSE %] >+ <input type="checkbox" name="batchremove" value="[% rule.id %]"/> >+ [% END %] >+ </td> >+ [% END %] >+ </tr> >+ [% END %] >+ </tbody> >+</table> >+</form> >+ >+<form action="[% script_name %]" method="post"> >+<input type="hidden" name="op" value="redo-matching" /> >+</form> >+ >+</div> >+</div> >+<div class="yui-b"> >+[% INCLUDE 'admin-menu.inc' %] >+</div> >+</div> >+[% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >index 1aeadf9..5fe2090 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >@@ -234,24 +234,12 @@ Cataloging: > > Exporting: > - >- - Include following fields when exporting BibTeX, >- - pref: BibtexExportAdditionalFields >- type: textarea >- - "Use one line per tag in the format BT_TAG: TAG$SUBFIELD ( e.g. lccn: 010$a )" >- - "<br/>" >- - "To specificy multiple marc tags/subfields as targets for a repeating BibTex tag, use the following format: BT_TAG: [TAG2$SUBFIELD1, TAG2$SUBFIELD2] ( e.g. notes: [501$a, 505$g] )" >- - "<br/>" >- - "All values of repeating tags and subfields will be printed with the given BibTeX tag." >- - "<br/>" >- - "Use '@' ( with quotes ) as the BT_TAG to replace the bibtex record type with a field value of your choosing." >- - >- - Include following fields when exporting RIS, >- - pref: RisExportAdditionalFields >- type: textarea >- - "Use one line per tag in the format RIS_TAG: TAG$SUBFIELD ( e.g. LC: 010$a )" >- - "<br/>" >- - "To specificy multiple marc tags/subfields as targets for a repeating RIS tag, use the following format: RIS_TAG: [TAG2$SUBFIELD1, TAG2$SUBFIELD2] ( e.g. NT: [501$a, 505$g] )" >- - "<br/>" >- - "All values of repeating tags and subfields will be printed with the given RIS tag." >- - "<br/>" >- - "Use of TY ( record type ) as a key will <i>replace</i> the default TY with the field value of your choosing." >+ - When importing records >+ - pref: MARCPermissions >+ choices: >+ yes: "use" >+ no: "don't use" >+ - MARC permissions rules to decide which action to take for each field. >+ - When <b>add_or_correct</b>, consider a field modification to be a correction if the old and new value has a similarity of >+ - pref: MARCPermissionsCorrectionSimilarity >+ - %. If similarity falls below this value, the new value will be treated as a new field. >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/logs.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/logs.pref >index 4aa5790..aa47f7a 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/logs.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/logs.pref >@@ -66,6 +66,12 @@ Logging: > on: Log > off: "Don't log" > - when reports are added, deleted or changed. >+ - >+ - pref: MARCPermissionsLog >+ choices: >+ on: Log >+ off: "Don't log" >+ - MARC permissions rule evaluations when applying rules for MARC field modifications. > Debugging: > - > - pref: DumpTemplateVarsIntranet >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt >index 7a50096..a053019 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt >@@ -545,6 +545,7 @@ function Changefwk() { > [% END %] > <input type="hidden" name="op" value="addbiblio" /> > <input type="hidden" id="frameworkcode" name="frameworkcode" value="[% frameworkcode %]" /> >+ <input type="hidden" name="z3950" value="[% z3950 %]" /> > <input type="hidden" name="biblionumber" value="[% biblionumber %]" /> > <input type="hidden" name="breedingid" value="[% breedingid %]" /> > <input type="hidden" name="changed_framework" value="" /> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/help/marc-permissions.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/help/marc-permissions.tt >new file mode 100644 >index 0000000..9f657e6 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/help/marc-permissions.tt >@@ -0,0 +1,148 @@ >+[% INCLUDE 'help-top.inc' %] >+ >+<h1>Manage MARC field permissions</h1> >+ >+<h3>Rule evaluation</h3> >+<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> >+<p>To add a field-specific rule (i.e. not a subfield), leave the <i>Subfield tag</i> field blank.</p> >+ >+<h4>Defaults</h4> >+<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> >+<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> >+ >+<h4>Wildcards</h4> >+<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> >+ >+<h4>Regular expressions</h4> >+<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> >+ >+<h4>Example rules</h4> >+<p>Following is an example rule set in order of specificity.</p> >+ >+<table> >+ <thead><tr> >+ <th>Tag</th> >+ <th>Subfield tag</th> >+ <th>Module</th> >+ <th>Filter</th> >+ <th>Description</th> >+ </tr></thead> >+ <tbody><tr> >+ <td>*</td> >+ <td></td> >+ <td>[% modules.0.name %]</td> >+ <td>*</td> >+ <td><i>Match any field regardless of [% modules.0.name %]</i></td> >+ </tr> >+ <tr> >+ <td>*</td> >+ <td>*</td> >+ <td>[% modules.0.name %]</td> >+ <td>*</td> >+ <td><i>Match any subfield regardless of [% modules.0.name %]</i></td> >+ </tr> >+ <tr> >+ <td>245</td> >+ <td>*</td> >+ <td>[% modules.0.name %]</td> >+ <td>z39.50</td> >+ <td><i>Match any subfield under <b>245</b> when [% modules.0.name %] is <b>z39.50</b></i></td> >+ </tr> >+ <tr> >+ <td>*</td> >+ <td>b</td> >+ <td>[% modules.0.name %]</td> >+ <td>z39.50</td> >+ <td><i>Match subfield <b>b</b> regardless of field when [% modules.0.name %] is <b>z39.50</b></i></td> >+ </tr> >+ <tr> >+ <td>500</td> >+ <td>[a-d]</td> >+ <td>[% modules.0.name %]</td> >+ <td>z39.50</td> >+ <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> >+ </tr> >+ <tr> >+ <td>5..</td> >+ <td>a</td> >+ <td>[% modules.0.name %]</td> >+ <td>z39.50</td> >+ <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> >+ </tr> >+ <tr> >+ <td>245</td> >+ <td>a</td> >+ <td>[% modules.0.name %]</td> >+ <td>z39.50</td> >+ <td><i>Match subfield <b>a</b> when field is <b>500</b> and [% modules.0.name %] is <b>z39.50</b></i></td> >+ </tr></tbody> >+</table> >+ >+<br> >+ >+<h3>Available filter modules</h3> >+<p>Filters cannot be regular expressions. Please use a single <b>*</b> as a wildcard if need.</p> >+ >+<table> >+ <thead><tr> >+ <th>Specificity</th> >+ <th>Module</th> >+ <th>Description</th> >+ </tr></thead> >+ <tbody> >+ [% FOREACH module IN modules %] >+ <tr> >+ <td>[% module.specificity %]</td> >+ <td>[% module.name %]</td> >+ <td>[% module.description %]</td> >+ </tr> >+ [% END %] >+ </tbody> >+</table> >+ >+<br> >+ >+<h3>Available sources</h3> >+<p>The following sources currently implement MARCPermissions.</p> >+ >+<table> >+ <thead><tr> >+ <th>Name</th> >+ <th>Description</th> >+ </tr></thead> >+ <tbody><tr> >+ <td>bulkmarcimport</td> >+ <td>bin/migration_tools/bulkmarcimport.pl</td> >+ </tr> >+ <tr> >+ <td>import_lexile</td> >+ <td>bin/migration_tools/import_lexile.pl</td> >+ </tr> >+ <tr> >+ <td>z39.50</td> >+ <td>Import from Z39.50 search in browser</td> >+ </tr> >+ <tr> >+ <td>intranet</td> >+ <td>Modifications from intranet in browser</td> >+ </tr> >+ <tr> >+ <td>batchmod</td> >+ <td>Batch record modification in browser</td> >+ </tr></tbody> >+</table> >+ >+<br> >+ >+<h3>Indicators</h3> >+<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> >+ >+<br> >+ >+<h3>Logging</h3> >+<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> >+<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> >+ >+<p><strong>See the full documentation for Koha in the <a href="http://manual.koha-community.org/[% helpVersion %]/en/">manual</a> (online).</strong></p> >+ >+[% INCLUDE 'help-bottom.inc' %] >diff --git a/misc/migration_tools/import_lexile.pl b/misc/migration_tools/import_lexile.pl >index e9fad46..3f89de4 100755 >--- a/misc/migration_tools/import_lexile.pl >+++ b/misc/migration_tools/import_lexile.pl >@@ -151,6 +151,7 @@ while ( my $row = $csv->getline_hr($fh) ) { > foreach my $biblionumber (@biblionumbers) { > $counter++; > my $record = GetMarcBiblio($biblionumber); >+ my $frameworkcode = GetFrameworkCode($biblionumber); > > if ($verbose) { > say "Found matching record! Biblionumber: $biblionumber"; >@@ -200,7 +201,7 @@ while ( my $row = $csv->getline_hr($fh) ) { > $record->append_fields($field); > } > >- ModBiblio( $record, $biblionumber ) unless ( $test ); >+ ModBiblio( $record, $biblionumber, $frameworkcode, {source => 'import_lexile'} ) unless ( $test ); > } > > } >diff --git a/t/db_dependent/Biblio/MARCPermissions.t b/t/db_dependent/Biblio/MARCPermissions.t >new file mode 100755 >index 0000000..d942680 >--- /dev/null >+++ b/t/db_dependent/Biblio/MARCPermissions.t >@@ -0,0 +1,280 @@ >+#!/usr/bin/perl >+ >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::More tests => 16; >+use Test::MockModule; >+ >+use MARC::Record; >+use Data::Dumper; >+ >+BEGIN { >+ use_ok('C4::Biblio'); >+} >+ >+# Start transaction >+my $dbh = C4::Context->dbh; >+$dbh->{AutoCommit} = 0; >+$dbh->{RaiseError} = 1; >+ >+C4::Context->set_preference( 'MARCPermissions', '1'); >+C4::Context->set_preference( 'MARCPermissionsLog', '1'); >+C4::Context->set_preference( 'MARCPermissionsCorrectionSimilarity', '90'); >+ >+# Create a record >+my $record = MARC::Record->new(); >+$record->append_fields ( >+ MARC::Field->new('008', '12345'), >+ MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'), >+ MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'), >+ MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'), >+ MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'), >+ MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'), >+); >+ >+# Add record to DB >+my ($biblionumber, $biblioitemnumber) = AddBiblio($record, ''); >+ >+my $modules = GetMarcPermissionsModules(); >+ >+############################################################################## >+# Test overwrite rule >+my $mod_record = MARC::Record->new(); >+$mod_record->append_fields ( >+ MARC::Field->new('008', '12345'), >+ MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'), >+ MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'), >+ MARC::Field->new('500', '1','1', 'a' => 'this field has now been changed'), >+ MARC::Field->new('500', '2','2', 'a' => 'and so have this field'), >+); >+ >+# Clear MARC permission rules from DB >+DelMarcPermissionsRule($_->{id}) for GetMarcPermissionsRules(); >+ >+# Add MARC permission rules to DB >+AddMarcPermissionsRule({ >+ module => $modules->[0]->{'id'}, >+ tagfield => '*', >+ tagsubfield => '', >+ filter => '*', >+ on_existing => 'overwrite', >+ on_new => 'add', >+ on_removed => 'remove' >+}); >+ >+my @log = (); >+my $new_record = ApplyMarcPermissions({ >+ biblionumber => $biblionumber, >+ record => $mod_record, >+ frameworkcode => '', >+ filter => {$modules->[0]->{'name'} => 'foo'}, >+ log => \@log >+ }); >+ >+my @a500 = $new_record->field('500'); >+is ($a500[0]->subfield('a'), 'this field has now been changed', 'old field is replaced when overwrite'); >+is ($a500[1]->subfield('a'), 'and so have this field', 'old field is replaced when overwrite'); >+ >+############################################################################## >+# Test remove rule >+is ($new_record->field('250'), undef, 'removed field is removed'); >+ >+############################################################################## >+# Test skip rule >+$mod_record = MARC::Record->new(); >+$mod_record->append_fields ( >+ MARC::Field->new('008', '12345'), >+ MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'), >+ MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'), >+ MARC::Field->new('500', '1','1', 'a' => 'this should not show'), >+ MARC::Field->new('500', '2','2', 'a' => 'and neither should this'), >+); >+ >+AddMarcPermissionsRule({ >+ module => $modules->[0]->{'id'}, >+ tagfield => '500', >+ tagsubfield => '*', >+ filter => '*', >+ on_existing => 'skip', >+ on_new => 'skip', >+ on_removed => 'skip' >+}); >+ >+@log = (); >+$new_record = ApplyMarcPermissions({ >+ biblionumber => $biblionumber, >+ record => $mod_record, >+ frameworkcode => '', >+ filter => {$modules->[0]->{'name'} => 'foo'}, >+ log => \@log >+ }); >+ >+@a500 = $new_record->field('500'); >+is ($a500[0]->subfield('a'), 'the lazy programmer jumps over the quick brown tests', 'old field is kept when skip'); >+is ($a500[1]->subfield('a'), 'the quick brown test jumps over the lazy programmers', 'old field is kept when skip'); >+ >+############################################################################## >+# Test add rule >+$mod_record = MARC::Record->new(); >+$mod_record->append_fields ( >+ MARC::Field->new('008', '12345'), >+ MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'), >+ MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'), >+ #MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'), >+ MARC::Field->new('245', '1','2', 'a' => 'some new fun value'), >+ MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'), >+ MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'), >+); >+ >+AddMarcPermissionsRule({ >+ module => $modules->[0]->{'id'}, >+ tagfield => '245', >+ tagsubfield => '*', >+ filter => '*', >+ on_existing => 'add', >+ on_new => 'add', >+ on_removed => 'skip' >+}); >+ >+ >+@log = (); >+$new_record = ApplyMarcPermissions({ >+ biblionumber => $biblionumber, >+ record => $mod_record, >+ frameworkcode => '', >+ filter => {$modules->[0]->{'name'} => 'foo'}, >+ log => \@log >+ }); >+ >+my @a245 = $new_record->field('245')->subfield('a'); >+is ($a245[0], 'field data for 245 a with indicators 12', 'old field is kept when adding new'); >+is ($a245[1], 'some new fun value', 'new field is added'); >+ >+############################################################################## >+# Test add_or_correct rule >+$mod_record = MARC::Record->new(); >+$mod_record->append_fields ( >+ MARC::Field->new('008', '12345'), >+ #MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'), >+ MARC::Field->new('100', '','', 'a' => 'a very different value'), >+ MARC::Field->new('250', '','', 'a' => '250 bottles of beer on the wall'), >+ #MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'), >+ MARC::Field->new('245', '1','2', 'a' => 'Field data for 245 a with indicators 12', 'a' => 'some very different value'), >+ MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'), >+ MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'), >+); >+ >+AddMarcPermissionsRule({ >+ module => $modules->[0]->{'id'}, >+ tagfield => '(100|245)', >+ tagsubfield => '*', >+ filter => '*', >+ on_existing => 'add_or_correct', >+ on_new => 'add', >+ on_removed => 'skip' >+}); >+ >+@log = (); >+$new_record = ApplyMarcPermissions({ >+ biblionumber => $biblionumber, >+ record => $mod_record, >+ frameworkcode => '', >+ filter => {$modules->[0]->{'name'} => 'foo'}, >+ log => \@log >+ }); >+ >+@a245 = $new_record->field('245')->subfield('a'); >+is ($a245[0], 'Field data for 245 a with indicators 12', 'add_or_correct modifies field when a correction'); >+is ($a245[1], 'some very different value', 'add_or_correct adds field when not a correction'); >+ >+my @a100 = $new_record->field('100')->subfield('a'); >+is ($a100[0], 'field data for 100 a without indicators', 'add_or_correct keeps old field when not a correction'); >+is ($a100[1], 'a very different value', 'add_or_correct adds field when not a correction'); >+ >+############################################################################## >+# Test rule evaluation order >+$mod_record = MARC::Record->new(); >+$mod_record->append_fields ( >+ MARC::Field->new('008', '12345'), >+ MARC::Field->new('100', '','', 'a' => 'field data for 100 a without indicators'), >+ MARC::Field->new('250', '','', 'a' => 'take one down, pass it around'), >+ MARC::Field->new('245', '1','2', 'a' => 'field data for 245 a with indicators 12'), >+ MARC::Field->new('500', '1','1', 'a' => 'the lazy programmer jumps over the quick brown tests'), >+ MARC::Field->new('500', '2','2', 'a' => 'the quick brown test jumps over the lazy programmers'), >+); >+ >+ >+DelMarcPermissionsRule($_->{id}) for GetMarcPermissionsRules(); >+ >+AddMarcPermissionsRule({ >+ module => $modules->[0]->{'id'}, >+ tagfield => '*', >+ tagsubfield => '*', >+ filter => '*', >+ on_existing => 'skip', >+ on_new => 'skip', >+ on_removed => 'skip' >+}); >+AddMarcPermissionsRule({ >+ module => $modules->[0]->{'id'}, >+ tagfield => '250', >+ tagsubfield => '*', >+ filter => '*', >+ on_existing => 'overwrite', >+ on_new => 'skip', >+ on_removed => 'skip' >+}); >+AddMarcPermissionsRule({ >+ module => $modules->[0]->{'id'}, >+ tagfield => '*', >+ tagsubfield => 'a', >+ filter => '*', >+ on_existing => 'add_or_correct', >+ on_new => 'skip', >+ on_removed => 'skip' >+}); >+AddMarcPermissionsRule({ >+ module => $modules->[0]->{'id'}, >+ tagfield => '250', >+ tagsubfield => 'a', >+ filter => '*', >+ on_existing => 'add', >+ on_new => 'skip', >+ on_removed => 'skip' >+}); >+ >+@log = (); >+$new_record = ApplyMarcPermissions({ >+ biblionumber => $biblionumber, >+ record => $mod_record, >+ frameworkcode => '', >+ filter => {$modules->[0]->{'name'} => 'foo'}, >+ log => \@log >+ }); >+ >+my @rule = grep { $_->{tag} eq '250' and $_->{subfieldcode} eq 'a' } @log; >+is(scalar @rule, 1, 'only one rule applied'); >+is($rule[0]->{event}.':'.$rule[0]->{action}, 'existing:add', 'most specific rule used'); >+ >+my @a250 = $new_record->field('250')->subfield('a'); >+is ($a250[0], '250 bottles of beer on the wall', 'most specific rule is applied, original field kept'); >+is ($a250[1], 'take one down, pass it around', 'most specific rule is applied, new field added'); >+ >+$dbh->rollback; >+ >+1; >diff --git a/tools/batch_record_modification.pl b/tools/batch_record_modification.pl >index 6ea0317..9f31527 100755 >--- a/tools/batch_record_modification.pl >+++ b/tools/batch_record_modification.pl >@@ -156,7 +156,7 @@ if ( $op eq 'form' ) { > my ( $job ); > if ( $runinbackground ) { > my $job_size = scalar( @record_ids ); >- $job = C4::BackgroundJob->new( $sessionID, "FIXME", '/cgi-bin/koha/tools/batch_record_modification.pl', $job_size ); >+ $job = C4::BackgroundJob->new( $sessionID, "FIXME", $ENV{SCRIPT_NAME}, $job_size ); > my $job_id = $job->id; > if (my $pid = fork) { > $dbh->{InactiveDestroy} = 1; >@@ -168,7 +168,7 @@ if ( $op eq 'form' ) { > } elsif (defined $pid) { > close STDOUT; > } else { >- warn "fork failed while attempting to run tools/batch_record_modification.pl as a background job"; >+ warn "fork failed while attempting to run $ENV{'SCRIPT_NAME'} as a background job"; > exit 0; > } > } >@@ -192,7 +192,14 @@ if ( $op eq 'form' ) { > my $record = GetMarcBiblio( $biblionumber ); > ModifyRecordWithTemplate( $mmtid, $record ); > my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber ); >- ModBiblio( $record, $biblionumber, $frameworkcode ); >+ my ($member) = C4::Members::GetMember('borrowernumber' => $loggedinuser); >+ ModBiblio( $record, $biblionumber, $frameworkcode, >+ { >+ source => 'batchmod', >+ category => $member->{'category_type'}, >+ borrower => $loggedinuser >+ } >+ ); > }; > if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well > push @messages, {
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 14957
:
44056
|
44057
|
44058
|
51723
|
51724
|
56637
|
56638
|
56643
|
56644
|
56645
|
56646
|
56647
|
56648
|
57114
|
57115
|
57116
|
57117
|
57118
|
57119
|
59811
|
59812
|
59885
|
60711
|
60824
|
60825
|
61065
|
61641
|
61736
|
64792
|
72498
|
74855
|
75118
|
76201
|
76202
|
76203
|
76204
|
76205
|
76206
|
76207
|
76208
|
76209
|
76210
|
78651
|
78652
|
78653
|
79367
|
79368
|
79369
|
79370
|
79890
|
80972
|
80973
|
80974
|
80975
|
81176
|
81177
|
81178
|
81179
|
81621
|
81699
|
81700
|
81705
|
81706
|
81707
|
81708
|
81969
|
81970
|
81971
|
82030
|
82031
|
82775
|
87709
|
87711
|
87771
|
87929
|
88205
|
88206
|
89961
|
90668
|
90669
|
90671
|
90672
|
90722
|
90723
|
90740
|
90741
|
92933
|
92934
|
92935
|
92936
|
92937
|
92938
|
92999
|
93499
|
94909
|
94911
|
99998
|
100052
|
100053
|
100054
|
100055
|
100056
|
100057
|
100058
|
100059
|
100060
|
100061
|
101510
|
109955
|
109956
|
109957
|
109958
|
109959
|
109960
|
109961
|
109962
|
109963
|
116622
|
116623
|
116624
|
116625
|
117844
|
117845
|
117849
|
117850
|
117853
|
117859
|
117964
|
117965
|
117966
|
118918
|
118919
|
118920
|
118921
|
118922
|
118923
|
118924
|
118925
|
118926
|
118927
|
118934
|
118935
|
118936
|
118937
|
118938
|
118939
|
118940
|
118941
|
118942
|
118943
|
118944
|
118945
|
118946
|
118957
|
120533
|
120534
|
120535
|
120536
|
120537
|
120538
|
120539
|
120540
|
120541
|
120542
|
120543
|
120544
|
120545
|
120546
|
120547
|
120548
|
120550
|
120553
|
120554
|
120555
|
120556
|
120557
|
120581
|
120582
|
120583
|
120584
|
120585
|
120586
|
120587
|
120588
|
120589
|
120590
|
120591
|
120592
|
120593
|
120594
|
120595
|
120596
|
120597
|
120598
|
120599
|
120600
|
120601
|
120602
|
120603
|
120604
|
120605
|
120670
|
120671
|
120705
|
125514
|
125515
|
125516
|
125517
|
125518
|
125519
|
125520
|
125521
|
125522
|
125523
|
125524
|
125525
|
125526
|
125527
|
125528
|
125529
|
125530
|
125531
|
125532
|
125533
|
125534
|
125535
|
125536
|
125537
|
125538
|
125539
|
125540
|
125632
|
125633
|
125636
|
126424
|
126425
|
126431
|
126592
|
126666
|
126667
|
126752
|
126753
|
126754
|
126755
|
126756
|
126757
|
126758
|
126759
|
126760
|
126761
|
126762
|
126763
|
126764
|
126765
|
126766
|
126767
|
126768
|
126769
|
126770
|
126771
|
126772
|
126773
|
126774
|
126775
|
126776
|
126777
|
126778
|
126779
|
126780
|
126781
|
126782
|
126783
|
126784
|
126785
|
126786
|
126950
|
126951