Bugzilla – Attachment 190647 Details for
Bug 16994
Import and export MARC modification templates
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 16994: Import and export MARC modification templates
Bug-16994-Import-and-export-MARC-modification-temp.patch (text/plain), 16.51 KB, created by
Roman Dolny
on 2025-12-19 20:03:41 UTC
(
hide
)
Description:
Bug 16994: Import and export MARC modification templates
Filename:
MIME Type:
Creator:
Roman Dolny
Created:
2025-12-19 20:03:41 UTC
Size:
16.51 KB
patch
obsolete
>From 12fd0eb54e6ea3f7048fd01fadfd842be399277b Mon Sep 17 00:00:00 2001 >From: Arthur Suzuki <arthur.suzuki@biblibre.com> >Date: Tue, 16 Dec 2025 12:59:20 +0000 >Subject: [PATCH] Bug 16994: Import and export MARC modification templates > >This patch adds export and import functions to the MarcModificationTemplates tools >The different templates can be exported either individually or all of them. >On import you can choose to either skip or overwrite existing templates with the same name. > >Test plan: >- create a Marc Modification templates with one field creation action. >- click on "Export all" in the menu and save the file on your computer. >- add a new action in the template (ie, update field). >- click on "Import" in the menu and choose the downloaded file. >- by default the import tool doesn't overwrite >- there should be a message saying "skipped 1 template" >- the template should still have 2 actions. >- import the same file again but uncheck the "skip existing template". >- this time the template should be back to only one action. >- delete the template >- import the same file again, the template should be back with a single action. > >Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl> >--- > C4/MarcModificationTemplates.pm | 191 ++++++++++++++++++ > .../tools/marc_modification_templates.tt | 81 ++++++++ > tools/marc_modification_templates.pl | 57 ++++++ > 3 files changed, 329 insertions(+) > >diff --git a/C4/MarcModificationTemplates.pm b/C4/MarcModificationTemplates.pm >index 100a4cce40..d2076ab487 100644 >--- a/C4/MarcModificationTemplates.pm >+++ b/C4/MarcModificationTemplates.pm >@@ -34,6 +34,7 @@ use Koha::SimpleMARC qw( > ); > use Koha::MoreUtils; > use Koha::DateUtils qw( dt_from_string ); >+use JSON qw( encode_json decode_json ); > > use vars qw(@ISA @EXPORT); > >@@ -54,6 +55,9 @@ BEGIN { > > ModifyRecordsWithTemplate > ModifyRecordWithTemplate >+ >+ ExportModificationTemplates >+ ImportModificationTemplates > ); > } > >@@ -735,6 +739,193 @@ sub ModifyRecordWithTemplate { > > return; > } >+ >+=head2 >+ ExportModificationTemplates >+ >+ my $json = ExportModificationTemplates( $template_ids ); >+ >+ Exports MARC modification templates to JSON format. >+ If $template_ids is provided, only export those templates. >+ If not provided, export all templates. >+ >+ Returns a JSON string containing the exported templates. >+=cut >+ >+sub ExportModificationTemplates { >+ my ($template_ids) = @_; >+ >+ my $dbh = C4::Context->dbh; >+ >+ my @templates; >+ if ( $template_ids && @$template_ids ) { >+ my $placeholders = join( ',', ('?') x @$template_ids ); >+ my $sth = $dbh->prepare( >+ "SELECT * FROM marc_modification_templates WHERE template_id IN ($placeholders) ORDER BY name"); >+ $sth->execute(@$template_ids); >+ while ( my $template = $sth->fetchrow_hashref() ) { >+ push( @templates, $template ); >+ } >+ } else { >+ my $sth = $dbh->prepare("SELECT * FROM marc_modification_templates ORDER BY name"); >+ $sth->execute(); >+ while ( my $template = $sth->fetchrow_hashref() ) { >+ push( @templates, $template ); >+ } >+ } >+ >+ my $export_data = { >+ version => 1, >+ templates => [], >+ }; >+ >+ foreach my $template (@templates) { >+ my $template_id = $template->{'template_id'}; >+ my @actions = GetModificationTemplateActions($template_id); >+ >+ my $template_export = { >+ template_id => $template_id, >+ name => $template->{'name'}, >+ actions => [], >+ }; >+ >+ foreach my $action (@actions) { >+ my $action_export = { >+ mmta_id => $action->{'mmta_id'}, >+ ordering => $action->{'ordering'}, >+ action => $action->{'action'}, >+ field_number => $action->{'field_number'}, >+ from_field => $action->{'from_field'}, >+ from_subfield => $action->{'from_subfield'}, >+ field_value => $action->{'field_value'}, >+ to_field => $action->{'to_field'}, >+ to_subfield => $action->{'to_subfield'}, >+ to_regex_search => $action->{'to_regex_search'}, >+ to_regex_replace => $action->{'to_regex_replace'}, >+ to_regex_modifiers => $action->{'to_regex_modifiers'}, >+ conditional => $action->{'conditional'}, >+ conditional_field => $action->{'conditional_field'}, >+ conditional_subfield => $action->{'conditional_subfield'}, >+ conditional_comparison => $action->{'conditional_comparison'}, >+ conditional_value => $action->{'conditional_value'}, >+ conditional_regex => $action->{'conditional_regex'}, >+ description => $action->{'description'}, >+ }; >+ >+ push( @{ $template_export->{actions} }, $action_export ); >+ } >+ >+ push( @{ $export_data->{templates} }, $template_export ); >+ } >+ >+ return encode_json($export_data); >+} >+ >+=head2 >+ ImportModificationTemplates >+ >+ my $result = ImportModificationTemplates( $json, $options ); >+ >+ Imports MARC modification templates from JSON format. >+ $json is the JSON string to import. >+ $options is a hashref with import options: >+ - skip_existing: if true, skip templates that already exist (default: false) >+ - update_existing: if true, update templates that already exist (default: false) >+ >+ Returns a hashref with: >+ - success: number of successfully imported templates >+ - skipped: number of skipped templates >+ - errors: arrayref of error messages >+=cut >+ >+sub ImportModificationTemplates { >+ my ( $json, $skip_existing ) = @_; >+ >+ my $result = { >+ success => 0, >+ skipped => 0, >+ errors => [], >+ }; >+ >+ my $data; >+ eval { $data = decode_json($json); }; >+ if ($@) { >+ push( @{ $result->{errors} }, "Failed to parse JSON: $@" ); >+ return $result; >+ } >+ >+ my $version = $data->{version} || 0; >+ if ( $version != 1 ) { >+ push( @{ $result->{errors} }, "Unsupported export version: $version" ); >+ return $result; >+ } >+ >+ my $templates = $data->{templates} || []; >+ my $dbh = C4::Context->dbh; >+ >+ foreach my $template_data (@$templates) { >+ my $template_name = $template_data->{name}; >+ my $actions = $template_data->{actions} || []; >+ >+ # Check if template already exists >+ my $sth = $dbh->prepare("SELECT template_id FROM marc_modification_templates WHERE name = ?"); >+ $sth->execute($template_name); >+ my $existing = $sth->fetchrow_hashref(); >+ >+ if ( $existing && $skip_existing ) { >+ $result->{skipped}++; >+ next; >+ } >+ >+ my $template_id; >+ if ($existing) { >+ $template_id = $existing->{template_id}; >+ >+ # Delete existing actions for this template >+ $sth = $dbh->prepare("DELETE FROM marc_modification_template_actions WHERE template_id = ?"); >+ $sth->execute($template_id); >+ $result->{overwrite}++; >+ } else { >+ >+ # Create new template >+ $sth = $dbh->prepare("INSERT INTO marc_modification_templates (name) VALUES (?)"); >+ $sth->execute($template_name); >+ >+ $sth = $dbh->prepare("SELECT template_id FROM marc_modification_templates WHERE name = ?"); >+ $sth->execute($template_name); >+ my $row = $sth->fetchrow_hashref(); >+ $template_id = $row->{template_id}; >+ } >+ >+ # Add new actions >+ foreach my $action_data (@$actions) { >+ AddModificationTemplateAction( >+ $template_id, >+ $action_data->{action}, >+ $action_data->{field_number}, >+ $action_data->{from_field}, >+ $action_data->{from_subfield}, >+ $action_data->{field_value}, >+ $action_data->{to_field}, >+ $action_data->{to_subfield}, >+ $action_data->{to_regex_search}, >+ $action_data->{to_regex_replace}, >+ $action_data->{to_regex_modifiers}, >+ $action_data->{conditional}, >+ $action_data->{conditional_field}, >+ $action_data->{conditional_subfield}, >+ $action_data->{conditional_comparison}, >+ $action_data->{conditional_value}, >+ $action_data->{conditional_regex}, >+ $action_data->{description}, >+ ); >+ } >+ >+ $result->{success}++; >+ } >+ >+ return $result; >+} > 1; > __END__ > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/marc_modification_templates.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/marc_modification_templates.tt >index 48089fc10e..1d3f079dda 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/marc_modification_templates.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/marc_modification_templates.tt >@@ -74,6 +74,17 @@ > <input type="hidden" name="op" value="cud-delete_template" /> > <button type="submit" class="btn btn-default confirm-delete-template"><i class="fa fa-fw fa-trash-can"></i> Delete template</button> > </form> >+ [% ELSE %] >+ <div class="btn-group"> >+ <a href="#" class="btn btn-default" data-bs-toggle="dropdown"><i class="fa fa-download"></i> Export</a> >+ <div class="dropdown-menu"> >+ <a class="dropdown-item" href="/cgi-bin/koha/tools/marc_modification_templates.pl?op=export&export_all=1">Export all templates</a> >+ [% FOREACH TemplatesLoo IN TemplatesLoop %] >+ <a class="dropdown-item" href="/cgi-bin/koha/tools/marc_modification_templates.pl?op=export&template_ids=[% TemplatesLoo.template_id | html %]">Export [% TemplatesLoo.name | html %]</a> >+ [% END %] >+ </div> >+ </div> >+ <a href="#" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#importTemplate"><i class="fa fa-upload"></i> Import</a> > [% END %] > </div> > >@@ -375,6 +386,69 @@ > </div> > </div> > </div> >+ >+ <!-- Modal to import templates --> >+ <div class="modal" id="importTemplate" tabindex="-1" role="dialog" aria-labelledby="LabelimportTemplate" aria-hidden="true"> >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h1 class="modal-title" id="LabelimportTemplate">Import MARC modification templates</h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <form method="post" action="/cgi-bin/koha/tools/marc_modification_templates.pl" enctype="multipart/form-data" id="import_template" class="validated"> >+ [% INCLUDE 'csrf-token.inc' %] >+ <div class="modal-body"> >+ <fieldset> >+ <p> >+ <label for="import_file" class="required">JSON file:</label> >+ <input type="file" name="import_file" id="import_file" required="required" class="required" /> >+ <span class="required">Required</span> >+ </p> >+ >+ <p> >+ <label> >+ <input type="checkbox" name="skip_existing" id="skip_existing" value="1" checked /> >+ Skip existing templates (do not overwrite) >+ </label> >+ </p> >+ >+ <input type="hidden" name="op" value="cud-import" /> >+ >+ [% IF import_error %] >+ [% IF import_error == 'no_file_uploaded' %] >+ <div class="alert alert-danger">Error: No file was uploaded.</div> >+ [% END %] >+ [% END %] >+ >+ [% IF import_success || import_skipped || import_overwrite %] >+ <div class="alert alert-success"> >+ [% IF import_success %] >+ <p>Successfully imported [% import_success | html %] template(s).</p> >+ [% END %] >+ [% IF import_skipped %] >+ <p>Skipped [% import_skipped | html %] template(s) that already exist.</p> >+ [% END %] >+ [% IF import_overwrite %] >+ <p>Overwrite [% import_overwrite | html %] template(s) that already exist.</p> >+ [% END %] >+ </div> >+ [% END %] >+ >+ [% IF import_errors %] >+ [% FOREACH error IN import_errors %] >+ <div class="alert alert-danger">Error: [% error | html %]</div> >+ [% END %] >+ [% END %] >+ </fieldset> >+ </div> >+ <div class="modal-footer"> >+ <button type="submit" class="btn btn-primary">Import</button> >+ <button type="button" class="btn btn-default" data-bs-dismiss="modal">Close</button> >+ </div> >+ </form> >+ </div> >+ </div> >+ </div> > [% END %] > > [% MACRO jsinclude BLOCK %] >@@ -384,6 +458,13 @@ > [% IF ActionsLoop %] > var mmtas = [% ActionsLoop.json | $raw %] > [% END %] >+ >+ // Show import modal if there are import results >+ [% IF show_import_modal %] >+ $(document).ready(function() { >+ $('#importTemplate').modal('show'); >+ }); >+ [% END %] > </script> > [% Asset.js("js/marc_modification_templates.js") | $raw %] > [% END %] >diff --git a/tools/marc_modification_templates.pl b/tools/marc_modification_templates.pl >index c130cc313e..2831af452c 100755 >--- a/tools/marc_modification_templates.pl >+++ b/tools/marc_modification_templates.pl >@@ -27,8 +27,10 @@ use C4::MarcModificationTemplates qw( > AddModificationTemplateAction > DelModificationTemplate > DelModificationTemplateAction >+ ExportModificationTemplates > GetModificationTemplateActions > GetModificationTemplates >+ ImportModificationTemplates > ModModificationTemplateAction > MoveModificationTemplateAction > ); >@@ -114,6 +116,61 @@ if ( $op eq "cud-create_template" ) { > > MoveModificationTemplateAction( scalar $cgi->param('mmta_id'), scalar $cgi->param('where') ); > >+} elsif ( $op eq "export" ) { >+ >+ my @template_ids; >+ if ( $cgi->param('export_all') ) { >+ >+ # Export all templates >+ my @templates = GetModificationTemplates(); >+ @template_ids = map { $_->{template_id} } @templates; >+ } else { >+ >+ # Export selected templates >+ @template_ids = $cgi->multi_param('template_ids'); >+ } >+ >+ my $json = ExportModificationTemplates( \@template_ids ); >+ >+ print $cgi->header( >+ -type => 'application/json', >+ -attachment => 'marc_modification_templates.json', >+ -charset => 'utf-8', >+ ); >+ print $json; >+ >+ exit; >+ >+} elsif ( $op eq "cud-import" ) { >+ >+ my $upload = $cgi->upload('import_file'); >+ if ($upload) { >+ my $json; >+ { >+ local $/; >+ while ( my $line = <$upload> ) { >+ $json .= $line; >+ } >+ } >+ >+ my $skip_existing = $cgi->param('skip_existing') ? 1 : 0; >+ >+ my $result = ImportModificationTemplates( $json, $skip_existing ); >+ >+ $template->param( >+ import_success => $result->{success}, >+ import_skipped => $result->{skipped}, >+ import_overwrite => $result->{overwrite}, >+ import_errors => $result->{errors}, >+ show_import_modal => 1, >+ ); >+ } else { >+ $template->param( >+ import_error => 'no_file_uploaded', >+ show_import_modal => 1, >+ ); >+ } >+ > } > > my @templates = GetModificationTemplates($template_id); >-- >2.39.5
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 16994
:
190560
|
190561
|
190563
| 190647 |
190648