From 12fd0eb54e6ea3f7048fd01fadfd842be399277b Mon Sep 17 00:00:00 2001 From: Arthur Suzuki 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 --- 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 @@ + [% ELSE %] +
+ Export + +
+ Import [% END %] @@ -375,6 +386,69 @@ + + + [% 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 %] [% 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