Bugzilla – Attachment 74855 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]
Bug 14957 - Rule based permission for MARC fields
Bug-14957---Rule-based-permission-for-MARC-fields.patch (text/plain), 68.40 KB, created by
David Gustafsson
on 2018-04-25 19:22:47 UTC
(
hide
)
Description:
Bug 14957 - Rule based permission for MARC fields
Filename:
MIME Type:
Creator:
David Gustafsson
Created:
2018-04-25 19:22:47 UTC
Size:
68.40 KB
patch
obsolete
>From b039e53ad2b7589979251b01e69a0a66ceb06e71 Mon Sep 17 00:00:00 2001 >From: David Gustafsson <david.gustafsson@ub.gu.se> >Date: Thu, 2 Feb 2017 13:36:38 +0100 >Subject: [PATCH] Bug 14957 - Rule based permission for MARC fields > >Add a rule based permission system for MARC field modifications. > >Test plan: > 1. Apply this patch > 2. Run updatedatabase.pl > 3. Log in to staff client > 4. Enable new syspref MARCPermissions > 5. You should see a new link "MARC field permissions" in the "Catalog" > section under "Koha administrtion". > 6. Go to the "MARC field permissions" page > 7. Next to the page heading ("Manage MARC field permissions") there is a > button with a question mark, click this for more information about the > rule system. > 8. Add desired rules to the rule table, press the +-button to add. > 9. Clicking the pen-icon (edit) should allow you to edit corresponding rule. > 10. Clicking the x-icon should remove corresponding rule after confirmation. > 11. Selecting one or more rules followed by clicking the trash-icon > should remove all selected rules after confirmation. > 12. To test your rules, import or in other way modify a record. > 13. Check that the record was modified as defined by your rules. > >Sponsored-by: Halland County Library >Sponsored-by: Catalyst IT >Sponsored-by: Gothenbug University Library >--- > C4/Biblio.pm | 617 ++++++++++++++++++++- > C4/ImportBatch.pm | 2 +- > C4/Installer/PerlDependencies.pm | 5 + > admin/marc-permissions.pl | 115 ++++ > cataloguing/addbiblio.pl | 12 +- > help.pl | 5 + > .../bug_14957-marc-permissions-syspref.sql | 3 + > .../atomicupdate/bug_14957-marc-permissions.sql | 31 ++ > .../intranet-tmpl/prog/en/includes/admin-menu.inc | 1 + > .../prog/en/modules/admin/admin-home.tt | 2 + > .../prog/en/modules/admin/marc-permissions.tt | 320 +++++++++++ > .../en/modules/admin/preferences/cataloguing.pref | 10 + > .../prog/en/modules/admin/preferences/logs.pref | 6 + > .../prog/en/modules/cataloguing/addbiblio.tt | 1 + > .../prog/en/modules/help/marc-permissions.tt | 148 +++++ > misc/migration_tools/bulkmarcimport.pl | 3 +- > misc/migration_tools/import_lexile.pl | 3 +- > t/db_dependent/Biblio/MARCPermissions.t | 280 ++++++++++ > tools/batch_record_modification.pl | 13 +- > 19 files changed, 1561 insertions(+), 16 deletions(-) > create mode 100755 admin/marc-permissions.pl > create mode 100644 installer/data/mysql/atomicupdate/bug_14957-marc-permissions-syspref.sql > create mode 100644 installer/data/mysql/atomicupdate/bug_14957-marc-permissions.sql > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-permissions.tt > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/help/marc-permissions.tt > create mode 100755 t/db_dependent/Biblio/MARCPermissions.t > >diff --git a/C4/Biblio.pm b/C4/Biblio.pm >index b9c46554b1..b7f35a2e22 100644 >--- a/C4/Biblio.pm >+++ b/C4/Biblio.pm >@@ -102,6 +102,12 @@ BEGIN { > > &CountItemsIssued > &CountBiblioInOrders >+ >+ &GetMarcPermissionsRules >+ &GetMarcPermissionsModules >+ &ModMarcPermissionsRule >+ &AddMarcPermissionsRule >+ &DelMarcPermissionsRule > ); > > # To modify something >@@ -129,6 +135,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 > ); > >@@ -266,7 +273,7 @@ sub AddBiblio { > > =head2 ModBiblio > >- ModBiblio( $record,$biblionumber,$frameworkcode); >+ ModBiblio($record, $biblionumber, $frameworkcode, $options); > > Replace an existing bib record identified by C<$biblionumber> > with one supplied by the MARC::Record object C<$record>. The embedded >@@ -287,7 +294,9 @@ Returns 1 on success 0 on failure > =cut > > sub ModBiblio { >- my ( $record, $biblionumber, $frameworkcode ) = @_; >+ my ( $record, $biblionumber, $frameworkcode, $options ) = @_; >+ $options //= {}; >+ > if (!$record) { > carp 'No record passed to ModBiblio'; > return 0; >@@ -315,6 +324,16 @@ sub ModBiblio { > > _strip_item_fields($record, $frameworkcode); > >+ # apply permissions >+ if (C4::Context->preference('MARCPermissions') && $biblionumber && defined $options && exists $options->{'context'}) { >+ $record = ApplyMarcPermissions({ >+ biblionumber => $biblionumber, >+ record => $record, >+ filter => $options->{'context'}, >+ } >+ ); >+ } >+ > # update biblionumber and biblioitemnumber in MARC > # FIXME - this is assuming a 1 to 1 relationship between > # biblios and biblioitems >@@ -325,13 +344,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); > > # 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 ); > > # modify the other koha tables > _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode ); >@@ -2573,7 +2592,7 @@ sub TransformHtmlToMarc { > > =head2 TransformMarcToKoha > >- $result = TransformMarcToKoha( $record, undef, $limit ) >+ $result = TransformMarcToKoha($record, undef, $biblionumber, $option) > > Extract data from a MARC bib record into a hashref representing > Koha biblio, biblioitems, and items fields. >@@ -2584,7 +2603,7 @@ hash_ref. > =cut > > sub TransformMarcToKoha { >- my ( $record, $frameworkcode, $limit_table ) = @_; >+ my ( $record, $frameworkcode, $limit_table, $biblionumber ) = @_; > # FIXME Parameter $frameworkcode is obsolete and will be removed > $limit_table //= q{}; > >@@ -3271,7 +3290,7 @@ sub _koha_delete_biblio_metadata { > > =head2 ModBiblioMarc > >- &ModBiblioMarc($newrec,$biblionumber,$frameworkcode); >+ &ModBiblioMarc($newrec, $biblionumber, $frameworkcode, $options); > > Add MARC XML data for a biblio to koha > >@@ -3283,6 +3302,7 @@ sub ModBiblioMarc { > # pass the MARC::Record to this function, and it will create the records in > # the marcxml field > my ( $record, $biblionumber, $frameworkcode ) = @_; >+ > if ( !$record ) { > carp 'ModBiblioMarc passed an undefined record'; > return; >@@ -3295,6 +3315,7 @@ sub ModBiblioMarc { > if ( !$frameworkcode ) { > $frameworkcode = ""; > } >+ > my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?"); > $sth->execute( $frameworkcode, $biblionumber ); > $sth->finish; >@@ -3596,8 +3617,588 @@ sub RemoveAllNsb { > return $record; > } > >-1; >+=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) = @_; >+ my $biblionumber = $arguments->{biblionumber}; >+ my $incoming_record = $arguments->{record}; >+ >+ if ( !$biblionumber ) { >+ carp 'ApplyMarcPermissions called on undefined biblionumber'; >+ return; >+ } >+ if ( !$incoming_record ) { >+ carp 'ApplyMarcPermissions called on undefined record'; >+ return; >+ } >+ my $old_record = GetMarcBiblio({ biblionumber => $biblionumber }); >+ >+ my $merge_rules = undef; >+ if ($old_record && $arguments->{filter} && ($merge_rules = GetMarcPermissions($arguments->{filter}))) { >+ return MergeRecords($old_record, $incoming_record, $merge_rules); >+ } >+ return $incoming_record; >+} >+ >+sub MergeRecords { >+ my ($old_record, $incoming_record, $merge_rules) = @_; >+ my $is_matching_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 $fields_by_tag = sub { >+ my ($record) = @_; >+ my $fields = {}; >+ foreach my $field ($record->fields()) { >+ $fields->{$field->tag()} //= []; >+ push @{$fields->{$field->tag()}}, $field; >+ } >+ return $fields; >+ }; >+ >+ my $hash_field_data = sub { >+ my ($field) = @_; >+ my $indicators = join("\x1E", map { $field->indicator($_) } (1, 2)); >+ return $indicators . "\x1E" . join("\x1E", sort map { join "\x1E", @{$_} } $field->subfields()); >+ }; >+ >+ my $diff_by_key = sub { >+ my ($a, $b) = @_; >+ my @removed; >+ my @intersecting; >+ my @added; >+ my %keys_index = map { $_ => undef } (keys %{$a}, keys %{$b}); >+ foreach my $key (keys %keys_index) { >+ if ($a->{$key} && $b->{$key}) { >+ push @intersecting, $a->{$key}; >+ } >+ elsif ($a->{$key}) { >+ push @removed, $a->{$key}; >+ } >+ else { >+ push @added, $b->{$key}; >+ } >+ } >+ return (\@removed, \@intersecting, \@added); >+ }; >+ >+ my $get_matching_field_rule = sub { >+ my ($tag) = @_; >+ my $matched_rule = undef; >+ # Exact match takes precedence >+ if (exists $merge_rules->{$tag}) { >+ $matched_rule = $merge_rules->{$tag}; >+ } >+ else { >+ # TODO: sorty by module/weight rule id or something, grab first matching via listutils thingy >+ my @matching_rules = map { $merge_rules->{$_} } grep { $is_matching_regex->($tag, $_) } sort keys %{$merge_rules}; >+ # TODO: fix >+ if (@matching_rules) { >+ $matched_rule = pop @matching_rules; >+ } >+ elsif($merge_rules->{'*'}) { >+ $matched_rule = $merge_rules->{'*'}; >+ } >+ } >+ return $matched_rule; >+ }; >+ >+ my $merged_record = MARC::Record->new(); >+ my @merged_record_fields; >+ >+ # Leader is always overwritten, or kept??? >+ $merged_record->leader($incoming_record->leader()); >+ >+ my $current_fields = $fields_by_tag->($old_record); >+ my $incoming_fields = $fields_by_tag->($incoming_record); >+ >+ # First we get all new incoming control fields >+ my @new_field_tags = grep { !(exists $current_fields->{$_}) } keys %{$incoming_fields}; >+ >+ foreach my $tag (@new_field_tags) { >+ my $rule = $get_matching_field_rule->($tag) // { >+ on_new => {'action' => 'skip', 'rule' => 0} >+ }; >+ if ( >+ $rule->{on_new}->{action} eq 'add' || >+ $rule->{on_new}->{action} eq 'overwrite' # ??? >+ ) { # Or could just be write/protect? >+ # Hmm, only one control field possible?? >+ push @merged_record_fields, @{$incoming_fields->{$tag}}; >+ } >+ } >+ >+ # Then we get all control fields no longer present in incoming fields >+ # (removed) >+ my @deleted_field_tags = grep { !(exists $incoming_fields->{$_}) } keys %{$current_fields}; >+ foreach my $tag (@deleted_field_tags) { >+ my $rule = $get_matching_field_rule->($tag) // { >+ on_deleted => {'action' => 'skip', 'rule' => 0} >+ }; >+ if ($rule->{on_deleted}->{action} eq 'skip') { >+ push @merged_record_fields, @{$current_fields->{$tag}}; >+ } >+ } >+ >+ # Then we get the intersection of control fields, present both in >+ # current and incoming record (possibly to be overwritten) >+ my @common_field_tags = grep { exists $incoming_fields->{$_} } keys %{$current_fields}; >+ foreach my $tag (@common_field_tags) { >+ # Is control field >+ my $rule = $get_matching_field_rule->($tag) // { >+ on_removed => {'action' => 'skip', 'rule' => 0}, >+ on_appended => {'action' => 'skip', 'rule' => 0} >+ }; >+ if ($tag < 10) { >+ # on_existing = on_match >+ if ($rule->{on_appended}->{action} eq 'skip') { # TODO: replace with "protect", "keep" >+ push @merged_record_fields, @{$current_fields->{$tag}}; >+ } >+ elsif ($rule->{on_appended}->{action} eq 'append') { >+ push @merged_record_fields, @{$incoming_fields->{$tag}}; >+ } >+ if ( >+ $rule->{on_appended}->{action} eq 'append' && >+ $rule->{on_removed}->{action} eq 'skip' >+ ) { >+ #TODO: This is an invalid combination for control fields, warn!! >+ # Or should perform client/server side validation to prevent this choice >+ } >+ } >+ else { >+ # Compute intersection and diff using field data >+ my %current_fields_by_data = map { $hash_field_data->($_) => $_ } @{$current_fields->{$tag}}; >+ my %incoming_fields_by_data = map { $hash_field_data->($_) => $_ } @{$incoming_fields->{$tag}}; >+ my ($current_fields_only, $common_fields, $incoming_fields_only) = $diff_by_key->(\%current_fields_by_data, \%incoming_fields_by_data); >+ >+ # First add common fields (intersection) >+ # Unchanged >+ if (@{$common_fields}) { >+ push @merged_record_fields, @{$common_fields}; >+ } >+ # Removed >+ if (@{$current_fields_only}) { >+ if ($rule->{on_removed}->{action} eq 'skip') { >+ push @merged_record_fields, @{$current_fields_only}; >+ } >+ } >+ # Appended >+ if (@{$incoming_fields_only}) { >+ if ($rule->{on_appended}->{action} eq 'append') { >+ push @merged_record_fields, @{$incoming_fields_only}; >+ } >+ } >+ } >+ } >+ if ($#merged_record_fields != 0) { >+ $merged_record->insert_fields_ordered(@merged_record_fields); >+ } >+ return $merged_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 ($filter) = @_; >+ my $dbh = C4::Context->dbh; >+ my $rule_count = 0; >+ my $modules = GetMarcPermissionsModules(); >+ # We only care about modules included in the context/filter >+ # TODO: Perhaps make sure source => '*' is default? >+ my @filter_modules = grep { exists $filter->{$_->{name}} } @{$modules}; >+ >+ my $cache = Koha::Caches->get_instance(); >+ my $permissions = $cache->get_from_cache('marc_permissions', { unsafe => 1 }); >+ >+ if (!$permissions) { >+ 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 $perm = $sth->fetchrow_hashref) { >+ my $target = ($permissions->{$perm->{name}}->{$perm->{filter}}->{$perm->{tagfield}} //= {}); >+ foreach my $event (GetMarcPermissionEvents()) { >+ $target->{$event} = { action => $perm->{$event}, rule => $perm->{'id'} }; >+ } >+ } >+ $cache->set_in_cache('marc_permissions', $permissions); >+ } >+ >+ my $filtered_permissions = undef; >+ foreach my $module (@filter_modules) { >+ if ( >+ exists $permissions->{$module->{name}} && >+ exists $permissions->{$module->{name}}->{$filter->{$module->{name}}} >+ ) { >+ # TODO: Support multiple overlapping filters/context?? >+ $filtered_permissions = $permissions->{$module->{name}}->{$filter->{$module->{name}}}; >+ last; >+ } >+ } >+ if (!$filtered_permissions) { >+ # No perms matching specific context conditions found, try wildcard value for each active context >+ foreach my $module (@filter_modules) { >+ if (exists $permissions->{$module->{name}}->{'*'}) { >+ $filtered_permissions = $permissions->{$module->{name}}->{'*'}; >+ last; >+ } >+ } >+ } >+ return $filtered_permissions; >+} >+ >+# TODO: Use this in GetMarcPermissions >+=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`.`filter`, >+ `marc_permissions`.`on_new`, >+ `marc_permissions`.`on_appended`, >+ `marc_permissions`.`on_removed`, >+ `marc_permissions`.`on_deleted`, >+ `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; >+} >+ >+sub GetMarcPermissionEvents { >+ return ('on_new', 'on_appended', 'on_removed', 'on_deleted'); >+} >+ >+=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 >+ * module - required >+ * filter - required >+ * on_new - required >+ * on_appended - required >+ * on_removed - required >+ * on_deleted - 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 = ?, >+ module = ?, >+ filter = ?, >+ on_new = ?, >+ on_appended = ?, >+ on_removed = ?, >+ on_deleted = ? >+ WHERE >+ id = ? >+ '; >+ my $sth = $dbh->prepare($query); >+ my $result = $sth->execute ( >+ $f->{tagfield}, >+ $f->{module}, >+ $f->{filter}, >+ $f->{on_new}, >+ $f->{on_appended}, >+ $f->{on_removed}, >+ $f->{on_deleted}, >+ $id >+ ); >+ ClearMarcPermissionsRulesCache(); >+ return $result; >+} >+ >+sub ClearMarcPermissionsRulesCache { >+ my $cache = Koha::Caches->get_instance(); >+ $cache->clear_from_cache('marc_permissions'); >+} >+ >+=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 >+ module - required >+ filter - required >+ on_new - required >+ on_appended - required >+ on_removed - required >+ on_deleted - 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, >+ module, >+ filter, >+ on_new, >+ on_appended, >+ on_removed, >+ on_deleted >+ ) >+ VALUES (?, ?, ?, ?, ?, ?, ?) >+ '; >+ my $sth = $dbh->prepare($query); >+ my $result = $sth->execute ( >+ $f->{tagfield}, >+ $f->{module}, >+ $f->{filter}, >+ $f->{on_new}, >+ $f->{on_appended}, >+ $f->{on_removed}, >+ $f->{on_deleted} >+ ); >+ ClearMarcPermissionsRulesCache(); >+ return $result; >+} >+ >+=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); >+ my $result = $sth->execute($id); >+ ClearMarcPermissionsRulesCache(); >+ return $result; >+} >+1; > > __END__ > >diff --git a/C4/ImportBatch.pm b/C4/ImportBatch.pm >index 1b74929c18..3f278c4717 100644 >--- a/C4/ImportBatch.pm >+++ b/C4/ImportBatch.pm >@@ -666,7 +666,7 @@ sub BatchCommitRecords { > } > $oldxml = $old_marc->as_xml($marc_type); > >- ModBiblio($marc_record, $recordid, $oldbiblio->frameworkcode); >+ ModBiblio($marc_record, $recordid, $oldbiblio->frameworkcode, {context => {source => 'batchimport'}}); > $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?"; > > if ($item_result eq 'create_new' || $item_result eq 'replace') { >diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm >index 84ba74d2fc..513f329b64 100644 >--- a/C4/Installer/PerlDependencies.pm >+++ b/C4/Installer/PerlDependencies.pm >@@ -867,6 +867,11 @@ our $PERL_DEPS = { > 'required' => '0', > 'min_ver' => '0.17', > }, >+ 'String::Similarity' => { >+ usage => 'cataloguing', >+ required => 1, >+ min_version => '1.04', >+ }, > }; > > 1; >diff --git a/admin/marc-permissions.pl b/admin/marc-permissions.pl >new file mode 100755 >index 0000000000..688b76b14c >--- /dev/null >+++ b/admin/marc-permissions.pl >@@ -0,0 +1,115 @@ >+#!/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 $rule_from_cgi = sub { >+ my ($cgi) = @_; >+ my %rule = map { $_ => scalar $cgi->param($_) } ( >+ 'tagfield', >+ 'module', >+ 'filter', >+ 'on_new', >+ 'on_appended', >+ 'on_removed', >+ 'on_deleted' >+ ); >+ return \%rule; >+}; >+ >+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 $rule = $rule_from_cgi->($input); >+ ModMarcPermissionsRule($id, $rule); >+ $rules = GetMarcPermissionsRules(); >+} >+elsif ( $op eq "add" ) { >+ my $rule = $rule_from_cgi->($input); >+ AddMarcPermissionsRule($rule); >+ $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 3d14f83e66..9875b65692 100755 >--- a/cataloguing/addbiblio.pl >+++ b/cataloguing/addbiblio.pl >@@ -41,6 +41,7 @@ use Koha::ItemTypes; > use Koha::Libraries; > > use Koha::BiblioFrameworks; >+use Koha::Patrons; > > use Date::Calc qw(Today); > use MARC::File::USMARC; >@@ -846,7 +847,15 @@ if ( $op eq "addbiblio" ) { > BiblioAutoLink( $record, $frameworkcode ); > } > if ( $is_a_modif ) { >- ModBiblio( $record, $biblionumber, $frameworkcode ); >+ my $member = Koha::Patrons->find($loggedinuser); >+ ModBiblio( $record, $biblionumber, $frameworkcode, { >+ context => { >+ source => $z3950 ? 'z39.50' : 'intranet', >+ category => $member->{'category_type'}, >+ borrower => $loggedinuser >+ } >+ } >+ ); > } > else { > ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode ); >@@ -939,6 +948,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 ad86a2da29..86855d0248 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 0000000000..d697fc3721 >--- /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 0000000000..c2c2f0720f >--- /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 IF NOT EXISTS `marc_permissions` ( >+ `id` int(11) NOT NULL auto_increment, >+ `tagfield` varchar(255) NOT NULL, -- can be regexe, so need > 3 chars >+ `module` int(11) NOT NULL, >+ `filter` varchar(255) NOT NULL, >+ `on_new` ENUM('skip', 'add') DEFAULT NULL, >+ `on_appended` ENUM('skip', 'append') DEFAULT NULL, >+ `on_removed` ENUM('skip', 'remove') DEFAULT NULL, >+ `on_deleted` ENUM('skip', 'delete') 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 5a08564ca1..efa589a8b2 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >@@ -36,6 +36,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 dd29587e97..1b3a2f50bf 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 >@@ -90,6 +90,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 0000000000..3189ebefb0 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/marc-permissions.tt >@@ -0,0 +1,320 @@ >+[% 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 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 */ >+ $('input[name="tagfield"]').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, "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>Module</th> >+ <th>Filter</th> >+ <th>On New</th> >+ <th>On Appended</th> >+ <th>On Removed</th> >+ <th>On Deleted</th> >+ <th>Actions</th> >+ <th> </th> >+ </tr></thead> >+ [% UNLESS edit %] >+ <tfoot> >+ <tr> >+ <th> </th> >+ <th><input type="text" size="5" name="tagfield"/></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_new"> >+ [% FOR on_new IN ['skip', 'add'] %] >+ <option value="[% on_new %]">[% on_new %]</option> >+ [% END %] >+ </select> >+ </th> >+ <th> >+ <select name="on_appended"> >+ [% FOR on_appended IN ['skip', 'append'] %] >+ <option value="[% on_appended %]">[% on_appended %]</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> >+ <select name="on_deleted"> >+ [% FOR on_deleted IN ['skip', 'delete'] %] >+ <option value="[% on_deleted %]">[% on_deleted %]</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> >+ <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_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_appended"> >+ [% FOR on_appended IN ['skip', 'append'] %] >+ [% IF on_appended == rule.on_append %] >+ <option value="[% on_appended %]" selected="selected">[% on_appended %]</option> >+ [% ELSE %] >+ <option value="[% on_appended %]">[% on_appended %]</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> >+ <select name="on_deleted"> >+ [% FOR on_deleted IN ['skip', 'delete'] %] >+ [% IF on_deleted == rule.on_deleted %] >+ <option value="[% on_deleted %]" selected="selected">[% on_deleted %]</option> >+ [% ELSE %] >+ <option value="[% on_deleted %]">[% on_deleted %]</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.module %]</td> >+ <td>[% rule.filter %]</td> >+ <td>[% rule.on_new %]</td> >+ <td>[% rule.on_appended %]</td> >+ <td>[% rule.on_removed %]</td> >+ <td>[% rule.on_deleted %]</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 0d61fe7b22..259d17cc37 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 >@@ -259,3 +259,13 @@ Cataloging: > - "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 cb896c61a5..0c2a1ffe8f 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 >@@ -72,6 +72,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 4b4516dbe3..fd4cde6cf2 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt >@@ -558,6 +558,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 0000000000..9f657e659b >--- /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/bulkmarcimport.pl b/misc/migration_tools/bulkmarcimport.pl >index 9a3b44c282..0e526331ac 100755 >--- a/misc/migration_tools/bulkmarcimport.pl >+++ b/misc/migration_tools/bulkmarcimport.pl >@@ -451,7 +451,7 @@ RECORD: while ( ) { > $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber; > }; > if ($update) { >- eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) }; >+ eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber), {context => {source => 'bulkmarcimport'}}) }; > if ($@) { > warn "ERROR: Edit biblio $biblionumber failed: $@\n"; > printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile); >@@ -844,4 +844,3 @@ If not specified, no MARC modification templates are used (default). > =back > > =cut >- >diff --git a/misc/migration_tools/import_lexile.pl b/misc/migration_tools/import_lexile.pl >index 131db44b12..73ffc5453d 100755 >--- a/misc/migration_tools/import_lexile.pl >+++ b/misc/migration_tools/import_lexile.pl >@@ -153,6 +153,7 @@ while ( my $row = $csv->getline_hr($fh) ) { > foreach my $biblionumber (@biblionumbers) { > $counter++; > my $record = GetMarcBiblio({ biblionumber => $biblionumber }); >+ my $frameworkcode = GetFrameworkCode($biblionumber); > > if ($verbose) { > say "Found matching record! Biblionumber: $biblionumber"; >@@ -202,7 +203,7 @@ while ( my $row = $csv->getline_hr($fh) ) { > $record->append_fields($field); > } > >- ModBiblio( $record, $biblionumber ) unless ( $test ); >+ ModBiblio( $record, $biblionumber, $frameworkcode, {context => {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 0000000000..d942680c14 >--- /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 bfdb7583f5..fcbca40b46 100755 >--- a/tools/batch_record_modification.pl >+++ b/tools/batch_record_modification.pl >@@ -158,7 +158,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; >@@ -170,7 +170,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; > } > } >@@ -194,7 +194,14 @@ if ( $op eq 'form' ) { > my $record = GetMarcBiblio({ biblionumber => $biblionumber }); > ModifyRecordWithTemplate( $mmtid, $record ); > my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber ); >- ModBiblio( $record, $biblionumber, $frameworkcode ); >+ my ($member) = Koha::Patrons->find($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, { >-- >2.11.0
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