Bug 8015: Add MARC Modifications Templates
authorKyle M Hall <kyle@bywatersolutions.com>
Thu, 26 Apr 2012 20:08:57 +0000 (16:08 -0400)
committerGalen Charlton <gmc@esilibrary.com>
Thu, 31 Oct 2013 22:47:08 +0000 (22:47 +0000)
The MARC Modification Templates system gives Koha users
the power to make alterations to MARC records automatically
while staging MARC records for import.

This tool is useful for altering MARC records from
various venders work with your MARC framework.

The system essentially allows one to create a basic script
using actions to Copy, Move, Add, Update and Delete fields.

Each action can also have an optional condition to check
the value or existance of another field.

The Copy & Move actions also support Regular Expressions,
which can be used to automatically modify field values during the
copy/move. An example would be to strip out the '$' character
in field 020$c.

Furthermore, the value for an update can include variables
that change each time the template is used. Currently,
the system supports two variables, __BRANCHCODE__ which
is replaced with the branchcode of the library currently
using the template, and __CURRENTDATE__ which is replaced
with the current date in ISO format ( YYYY-MM-DD ).

At its simplist, it can perform functions such as:
Copy field 092$a to 952$c
At its most complex it can run actions like:
Copy field 020$c to 020$c using RegEx s/\$// if 020$c equals RegEx m/^\$/

Signed-off-by: Leila <koha.aixmarseille@gmail.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
14 files changed:
C4/ImportBatch.pm
C4/Koha.pm
Koha/MarcModificationTemplates.pm [new file with mode: 0644]
Koha/SimpleMARC.pm [new file with mode: 0644]
installer/data/mysql/en/mandatory/userpermissions.sql
installer/data/mysql/kohastructure.sql
installer/data/mysql/updatedatabase.pl
koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc
koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt
koha-tmpl/intranet-tmpl/prog/en/modules/tools/marc_modification_templates.tt [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/en/modules/tools/stage-marc-import.tt
koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
tools/marc_modification_templates.pl [new file with mode: 0755]
tools/stage-marc-import.pl

index 61d5b86..02f2424 100644 (file)
@@ -26,6 +26,7 @@ use C4::Biblio;
 use C4::Items;
 use C4::Charset;
 use C4::AuthoritiesMarc;
+use Koha::MarcModificationTemplates;
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 
@@ -316,7 +317,7 @@ sub ModAuthInBatch {
 =head2 BatchStageMarcRecords
 
   ($batch_id, $num_records, $num_items, @invalid_records) = 
-    BatchStageMarcRecords($record_type, $encoding, $marc_records, $file_name,
+    BatchStageMarcRecords($encoding, $marc_records, $file_name, $marc_modification_template,
                           $comments, $branch_code, $parse_items,
                           $leave_as_staging, 
                           $progress_interval, $progress_callback);
@@ -328,6 +329,7 @@ sub  BatchStageMarcRecords {
     my $encoding = shift;
     my $marc_records = shift;
     my $file_name = shift;
+    my $marc_modification_template = shift;
     my $comments = shift;
     my $branch_code = shift;
     my $parse_items = shift;
@@ -378,6 +380,8 @@ sub  BatchStageMarcRecords {
 
         $encoding = $charset_guessed unless $encoding;
 
+        ModifyRecordWithTemplate( $marc_modification_template, $marc_record ) if ( $marc_modification_template );
+
         my $import_record_id;
         if (scalar($marc_record->fields()) == 0) {
             push @invalid_records, $marc_blob;
index 937ac5f..28ac1aa 100644 (file)
@@ -1486,6 +1486,23 @@ sub _isbn_cleanup {
     return;
 }
 
+=head2
+
+  Log( $message );
+
+  Writes data to /tmp/koha.log.
+
+  This is useful for debugging forked processes
+  that do not write to the apache error log
+
+=cut
+
+sub Log {
+  my ($data) = @_;
+  open (MYFILE, '>>/tmp/koha.log');
+  print MYFILE "$data\n";
+  close (MYFILE);
+}
 1;
 
 __END__
diff --git a/Koha/MarcModificationTemplates.pm b/Koha/MarcModificationTemplates.pm
new file mode 100644 (file)
index 0000000..86a03e6
--- /dev/null
@@ -0,0 +1,579 @@
+package Koha::MarcModificationTemplates;
+
+# Copyright 2010 Kyle M Hall <kyle.m.hall@gmail.com>
+#
+# 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 2 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+## NOTE:
+## Parts of this module are used from cgi scripts that are detached from apache before
+## execution. For this reason, the C4::Koha::Log function has been used to capture
+## output for debugging purposes.
+
+use strict;
+use warnings;
+
+use DateTime;
+
+use C4::Context;
+use Koha::SimpleMARC;
+
+use vars qw($VERSION @ISA @EXPORT);
+
+use constant DEBUG => 0;
+
+BEGIN {
+       $VERSION = 1.00;        # set the version for version checking
+       @ISA = qw(Exporter);
+       @EXPORT = qw(
+               &GetModificationTemplates
+               &AddModificationTemplate
+               &DelModificationTemplate
+
+               &GetModificationTemplateAction
+               &GetModificationTemplateActions
+
+               &AddModificationTemplateAction
+               &ModModificationTemplateAction
+               &DelModificationTemplateAction
+               &MoveModificationTemplateAction
+
+                &ModifyRecordsWithTemplate
+               &ModifyRecordWithTemplate
+       );
+}
+
+
+=head1 NAME
+
+Koha::MarcModificationTemplates - Module to manage MARC Modification Templates
+
+=head1 DESCRIPTION
+
+MARC Modification Templates are a tool for marc batch imports,
+so that librarians can set up templates for various vendors'
+files telling Koha what fields to insert data into.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 GetModificationTemplates
+
+  my @templates = GetModificationTemplates( [ $template_id ] );
+
+  Passing a $template_id will mark the given id as the selected template.
+=cut
+
+sub GetModificationTemplates {
+  my ( $template_id ) = @_;
+  C4::Koha::Log("Koha::MarcModificationTemplates::GetModificationTemplates( $template_id )") if DEBUG;
+  warn("Koha::MarcModificationTemplates::GetModificationTemplates( $template_id )") if DEBUG;
+
+  my $dbh = C4::Context->dbh;
+  my $sth = $dbh->prepare("SELECT * FROM marc_modification_templates");
+  $sth->execute();
+
+  my @templates;
+  while ( my $template = $sth->fetchrow_hashref() ) {
+    $template->{'selected'} = 1 if ( $template->{'template_id'} eq $template_id );
+    push( @templates, $template );
+  }
+
+  return @templates;
+}
+
+=head2
+  AddModificationTemplate
+
+  $template_id = AddModificationTemplate( $template_name[, $template_id ] );
+
+  If $template_id is supplied, the actions from that template will be copied
+  into the newly created template.
+=cut
+
+sub AddModificationTemplate {
+  my ( $template_name, $template_id_copy ) = @_;
+
+  my $dbh = C4::Context->dbh;
+  my $sth = $dbh->prepare("INSERT INTO marc_modification_templates ( name ) VALUES ( ? )");
+  $sth->execute( $template_name );
+
+  $sth = $dbh->prepare("SELECT * FROM marc_modification_templates WHERE name = ?");
+  $sth->execute( $template_name );
+  my $row = $sth->fetchrow_hashref();
+  my $template_id = $row->{'template_id'};
+
+  if ( $template_id_copy ) {
+    my @actions = GetModificationTemplateActions( $template_id_copy );
+    foreach my $action ( @actions ) {
+      AddModificationTemplateAction(
+        $template_id,
+        $action->{'action'},
+        $action->{'field_number'},
+        $action->{'from_field'},
+        $action->{'from_subfield'},
+        $action->{'field_value'},
+        $action->{'to_field'},
+        $action->{'to_subfield'},
+        $action->{'to_regex'},
+        $action->{'conditional'},
+        $action->{'conditional_field'},
+        $action->{'conditional_subfield'},
+        $action->{'conditional_comparison'},
+        $action->{'conditional_value'},
+        $action->{'conditional_regex'},
+        $action->{'description'},
+      );
+
+    }
+  }
+
+  return $template_id;
+}
+
+=head2
+  DelModificationTemplate
+
+  DelModificationTemplate( $template_id );
+=cut
+
+sub DelModificationTemplate {
+  my ( $template_id ) = @_;
+
+  my $dbh = C4::Context->dbh;
+  my $sth = $dbh->prepare("DELETE FROM marc_modification_templates WHERE template_id = ?");
+  $sth->execute( $template_id );
+
+  $sth = $dbh->prepare("DELETE FROM marc_modification_template_actions WHERE template_id = ?");
+  $sth->execute( $template_id );
+}
+
+=head2
+  GetModificationTemplateAction
+
+  my $action = GetModificationTemplateAction( $mmta_id );
+=cut
+
+sub GetModificationTemplateAction {
+  my ( $mmta_id ) = @_;
+
+  my $dbh = C4::Context->dbh;
+  my $sth = $dbh->prepare("SELECT * FROM marc_modification_template_actions WHERE mmta_id = ?");
+  $sth->execute( $mmta_id );
+  my $action = $sth->fetchrow_hashref();
+
+  return $action;
+}
+
+=head2
+  GetModificationTemplateActions
+
+  my @actions = GetModificationTemplateActions( $template_id );
+=cut
+
+sub GetModificationTemplateActions {
+  my ( $template_id ) = @_;
+
+  C4::Koha::Log( "Koha::MarcModificationTemplates::GetModificationTemplateActions( $template_id )" ) if DEBUG;
+  warn( "Koha::MarcModificationTemplates::GetModificationTemplateActions( $template_id )" ) if DEBUG;
+
+  my $dbh = C4::Context->dbh;
+  my $sth = $dbh->prepare("SELECT * FROM marc_modification_template_actions WHERE template_id = ? ORDER BY ordering");
+  $sth->execute( $template_id );
+
+  my @actions;
+  while ( my $action = $sth->fetchrow_hashref() ) {
+    push( @actions, $action );
+  }
+
+  C4::Koha::Log( Data::Dumper::Dumper( @actions ) ) if DEBUG > 4;
+  warn( Data::Dumper::Dumper( @actions ) ) if DEBUG > 4;
+
+  return @actions;
+}
+
+=head2
+  AddModificationTemplateAction
+
+  AddModificationTemplateAction(
+    $template_id, $action, $field_number,
+    $from_field, $from_subfield, $field_value,
+    $to_field, $to_subfield, $to_regex,
+    $conditional, $conditional_field, $conditional_subfield,
+    $conditional_comparison, $conditional_value,
+    $conditional_regex, $description
+  );
+
+  Adds a new action to the given modification template.
+
+=cut
+
+sub AddModificationTemplateAction {
+  my (
+    $template_id,
+    $action,
+    $field_number,
+    $from_field,
+    $from_subfield,
+    $field_value,
+    $to_field,
+    $to_subfield,
+    $to_regex,
+    $conditional,
+    $conditional_field,
+    $conditional_subfield,
+    $conditional_comparison,
+    $conditional_value,
+    $conditional_regex,
+    $description
+  ) = @_;
+
+  C4::Koha::Log( "Koha::MarcModificationTemplates::AddModificationTemplateAction( $template_id, $action,
+                    $field_number, $from_field, $from_subfield, $field_value, $to_field, $to_subfield,
+                    $to_regex, $conditional, $conditional_field, $conditional_subfield, $conditional_comparison,
+                    $conditional_value, $conditional_regex, $description )" ) if DEBUG;
+  warn( "Koha::MarcModificationTemplates::AddModificationTemplateAction( $template_id, $action,
+                    $field_number, $from_field, $from_subfield, $field_value, $to_field, $to_subfield,
+                    $to_regex, $conditional, $conditional_field, $conditional_subfield, $conditional_comparison,
+                    $conditional_value, $conditional_regex, $description )" ) if DEBUG;
+
+  $conditional_regex ||= '0';
+
+  my $dbh = C4::Context->dbh;
+  my $sth = $dbh->prepare( 'SELECT MAX(ordering) + 1 AS next_ordering FROM marc_modification_template_actions WHERE template_id = ?' );
+  $sth->execute( $template_id );
+  my $row = $sth->fetchrow_hashref;
+  my $ordering = $row->{'next_ordering'} || 1;
+
+  my $query = "
+  INSERT INTO marc_modification_template_actions (
+  mmta_id,
+  template_id,
+  ordering,
+  action,
+  field_number,
+  from_field,
+  from_subfield,
+  field_value,
+  to_field,
+  to_subfield,
+  to_regex,
+  conditional,
+  conditional_field,
+  conditional_subfield,
+  conditional_comparison,
+  conditional_value,
+  conditional_regex,
+  description
+  )
+  VALUES ( NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )";
+
+  $sth = $dbh->prepare( $query );
+
+  $sth->execute(
+    $template_id,
+    $ordering,
+    $action,
+    $field_number,
+    $from_field,
+    $from_subfield,
+    $field_value,
+    $to_field,
+    $to_subfield,
+    $to_regex,
+    $conditional,
+    $conditional_field,
+    $conditional_subfield,
+    $conditional_comparison,
+    $conditional_value,
+    $conditional_regex,
+    $description
+  );
+}
+
+=head2
+  ModModificationTemplateAction
+
+  ModModificationTemplateAction(
+    $mmta_id, $action, $field_number, $from_field,
+    $from_subfield, $field_value, $to_field,
+    $to_subfield, $to_regex, $conditional,
+    $conditional_field, $conditional_subfield,
+    $conditional_comparison, $conditional_value,
+    $conditional_regex, $description
+  );
+
+  Modifies an existing action.
+
+=cut
+
+sub ModModificationTemplateAction {
+  my (
+    $mmta_id,
+    $action,
+    $field_number,
+    $from_field,
+    $from_subfield,
+    $field_value,
+    $to_field,
+    $to_subfield,
+    $to_regex,
+    $conditional,
+    $conditional_field,
+    $conditional_subfield,
+    $conditional_comparison,
+    $conditional_value,
+    $conditional_regex,
+    $description
+  ) = @_;
+
+  my $dbh = C4::Context->dbh;
+
+  my $query = "
+  UPDATE marc_modification_template_actions SET
+  action = ?,
+  field_number = ?,
+  from_field = ?,
+  from_subfield = ?,
+  field_value = ?,
+  to_field = ?,
+  to_subfield = ?,
+  to_regex = ?,
+  conditional = ?,
+  conditional_field = ?,
+  conditional_subfield = ?,
+  conditional_comparison = ?,
+  conditional_value = ?,
+  conditional_regex = ?,
+  description = ?
+  WHERE mmta_id = ?";
+
+  my $sth = $dbh->prepare( $query );
+
+  $sth->execute(
+    $action,
+    $field_number,
+    $from_field,
+    $from_subfield,
+    $field_value,
+    $to_field,
+    $to_subfield,
+    $to_regex,
+    $conditional,
+    $conditional_field,
+    $conditional_subfield,
+    $conditional_comparison,
+    $conditional_value,
+    $conditional_regex,
+    $description,
+    $mmta_id
+  );
+}
+
+
+=head2
+  DelModificationTemplateAction
+
+  DelModificationTemplateAction( $mmta_id );
+
+  Deletes the given template action.
+=cut
+
+sub DelModificationTemplateAction {
+  my ( $mmta_id ) = @_;
+
+  my $action = GetModificationTemplateAction( $mmta_id );
+
+  my $dbh = C4::Context->dbh;
+  my $sth = $dbh->prepare("DELETE FROM marc_modification_template_actions WHERE mmta_id = ?");
+  $sth->execute( $mmta_id );
+
+  $sth = $dbh->prepare("UPDATE marc_modification_template_actions SET ordering = ordering - 1 WHERE template_id = ? AND ordering > ?");
+  $sth->execute( $action->{'template_id'}, $action->{'ordering'} );
+}
+
+=head2
+  MoveModificationTemplateAction
+
+  MoveModificationTemplateAction( $mmta_id, $where );
+
+  Changes the order for the given action.
+  Options for $where are 'up', 'down', 'top' and 'bottom'
+=cut
+sub MoveModificationTemplateAction {
+  my ( $mmta_id, $where ) = @_;
+
+  my $action = GetModificationTemplateAction( $mmta_id );
+
+  return if ( $action->{'ordering'} eq '1' && ( $where eq 'up' || $where eq 'top' ) );
+  return if ( $action->{'ordering'} eq GetModificationTemplateActions( $action->{'template_id'} ) && ( $where eq 'down' || $where eq 'bottom' ) );
+
+  my $dbh = C4::Context->dbh;
+  my ( $sth, $query );
+
+  if ( $where eq 'up' || $where eq 'down' ) {
+
+    ## For up and down, we just swap the ordering number with the one above or below it.
+
+    ## Change the ordering for the other action
+    $query = "UPDATE marc_modification_template_actions SET ordering = ? WHERE template_id = ? AND ordering = ?";
+
+    my $ordering = $action->{'ordering'};
+    $ordering-- if ( $where eq 'up' );
+    $ordering++ if ( $where eq 'down' );
+
+    $sth = $dbh->prepare( $query );
+    $sth->execute( $action->{'ordering'}, $action->{'template_id'}, $ordering );
+
+    ## Change the ordering for this action
+    $query = "UPDATE marc_modification_template_actions SET ordering = ? WHERE mmta_id = ?";
+    $sth = $dbh->prepare( $query );
+    $sth->execute( $ordering, $action->{'mmta_id'} );
+
+  } elsif ( $where eq 'top' ) {
+
+    $sth = $dbh->prepare('UPDATE marc_modification_template_actions SET ordering = ordering + 1 WHERE template_id = ? AND ordering < ?');
+    $sth->execute( $action->{'template_id'}, $action->{'ordering'} );
+
+    $sth = $dbh->prepare('UPDATE marc_modification_template_actions SET ordering = 1 WHERE mmta_id = ?');
+    $sth->execute( $mmta_id );
+
+  } elsif ( $where eq 'bottom' ) {
+
+    my $ordering = GetModificationTemplateActions( $action->{'template_id'} );
+
+    $sth = $dbh->prepare('UPDATE marc_modification_template_actions SET ordering = ordering - 1 WHERE template_id = ? AND ordering > ?');
+    $sth->execute( $action->{'template_id'}, $action->{'ordering'} );
+
+    $sth = $dbh->prepare('UPDATE marc_modification_template_actions SET ordering = ? WHERE mmta_id = ?');
+    $sth->execute( $ordering, $mmta_id );
+
+  }
+
+}
+
+=head2
+  ModifyRecordsWithTemplate
+
+  ModifyRecordsWithTemplate( $template_id, $batch );
+
+  Accepts a template id and a MARC::Batch object.
+=cut
+
+sub ModifyRecordsWithTemplate {
+  my ( $template_id, $batch ) = @_;
+  C4::Koha::Log( "Koha::MarcModificationTemplates::ModifyRecordsWithTemplate( $template_id, $batch )" ) if DEBUG;
+  warn( "Koha::MarcModificationTemplates::ModifyRecordsWithTemplate( $template_id, $batch )" ) if DEBUG;
+
+  while ( my $record = $batch->next() ) {
+    ModifyRecordWithTemplate( $template_id, $record );
+  }
+}
+
+=head2
+  ModifyRecordWithTemplate
+
+  ModifyRecordWithTemplate( $template_id, $record )
+
+  Accepts a MARC::Record object ( $record ) and modifies
+  it based on the actions for the given $template_id
+=cut
+
+sub ModifyRecordWithTemplate {
+  my ( $template_id, $record ) = @_;
+  C4::Koha::Log( "Koha::MarcModificationTemplates::ModifyRecordWithTemplate( $template_id, $record )" ) if DEBUG;
+  warn( "Koha::MarcModificationTemplates::ModifyRecordWithTemplate( $template_id, $record )" ) if DEBUG;
+  C4::Koha::Log( "Unmodified Record:\n" . $record->as_formatted() ) if DEBUG >= 10;
+  warn( "Unmodified Record:\n" . $record->as_formatted() ) if DEBUG >= 10;
+
+  my $current_date = DateTime->now()->ymd();
+  my $branchcode = C4::Context->userenv->{branch};
+
+  my @actions = GetModificationTemplateActions( $template_id );
+
+  foreach my $a ( @actions ) {
+    my $action = $a->{'action'};
+    my $field_number = $a->{'field_number'};
+    my $from_field = $a->{'from_field'};
+    my $from_subfield = $a->{'from_subfield'};
+    my $field_value = $a->{'field_value'};
+    my $to_field = $a->{'to_field'};
+    my $to_subfield = $a->{'to_subfield'};
+    my $to_regex = $a->{'to_regex'};
+    my $conditional = $a->{'conditional'};
+    my $conditional_field = $a->{'conditional_field'};
+    my $conditional_subfield = $a->{'conditional_subfield'};
+    my $conditional_comparison = $a->{'conditional_comparison'};
+    my $conditional_value = $a->{'conditional_value'};
+    my $conditional_regex = $a->{'conditional_regex'};
+
+    my $eval = "$action( \$record, '$from_field', '$from_subfield', ";
+
+    if ( $field_value ) {
+      C4::Koha::Log( "Field value before replacements: $field_value" ) if ( DEBUG >= 3 );
+      warn( "Field value before replacements: $field_value" ) if ( DEBUG >= 3 );
+
+      $field_value =~ s/__CURRENTDATE__/$current_date/g;
+      $field_value =~ s/__BRANCHCODE__/$branchcode/g;
+
+      $eval .= " '$field_value' ";
+
+      C4::Koha::Log( "Field value after replacements: $field_value" ) if ( DEBUG >= 3 );
+      warn( "Field value after replacements: $field_value" ) if ( DEBUG >= 3 );
+    } elsif ( $to_field ) {
+      $eval .= " '$to_field', '$to_subfield', '$to_regex' ";
+    }
+
+    $eval .= ", '$field_number' " if ( $field_number );
+    $eval .= ') ';
+
+    if ( $conditional ) {
+      $eval .= " $conditional ( ";
+
+      if ( $conditional_comparison eq 'exists' ) {
+        $eval .= "field_exists( \$record, '$conditional_field', '$conditional_subfield' )";
+
+      } elsif ( $conditional_comparison eq 'not_exists' ) {
+        $eval .= "!field_exists( \$record, '$conditional_field', '$conditional_subfield' )";
+
+      } elsif ( $conditional_comparison eq 'equals' ) {
+        $eval .= "field_equals( \$record, '$conditional_value', '$conditional_field', '$conditional_subfield', '$conditional_regex' ) ";
+
+      } elsif ( $conditional_comparison eq 'not_equals' ) {
+        $eval .= "!field_equals( \$record, '$conditional_value', '$conditional_field', '$conditional_subfield', '$conditional_regex' ) ";
+      }
+
+      $eval .= " )";
+    }
+
+    $eval .= ";";
+
+    C4::Koha::Log("eval $eval") if DEBUG >= 2;
+    warn("eval $eval") if DEBUG >= 2;
+    eval $eval;
+    C4::Koha::Log( $record->as_formatted() ) if DEBUG >= 10;
+    warn( $record->as_formatted() ) if DEBUG >= 10;
+
+  }
+}
+1;
+__END__
+
+=head1 AUTHOR
+
+Kyle M Hall
+
+=cut
diff --git a/Koha/SimpleMARC.pm b/Koha/SimpleMARC.pm
new file mode 100644 (file)
index 0000000..96a3af1
--- /dev/null
@@ -0,0 +1,331 @@
+package Koha::SimpleMARC;
+
+# Copyright 2009 Kyle M. Hall <kyle.m.hall@gmail.com>
+
+use strict;
+use warnings;
+
+#use MARC::Record;
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+our %EXPORT_TAGS = ( 'all' => [ qw(
+
+) ] );
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+our @EXPORT = qw(
+  read_field
+  update_field
+  copy_field
+  move_field
+  delete_field
+  field_exists
+  field_equals
+);
+
+our $VERSION = '0.01';
+
+our $debug = 0;
+
+=head1 NAME
+
+SimpleMARC - Perl modle for making simple MARC record alterations.
+
+=head1 SYNOPSIS
+
+  use SimpleMARC;
+
+=head1 DESCRIPTION
+
+SimpleMARC is designed to make writing scripts
+to modify MARC records simple and easy.
+
+Every function in the modules requires a
+MARC::Record object as its first parameter.
+
+=head1 AUTHOR
+
+Kyle Hall <lt>kyle.m.hall@gmail.com<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by Kyle Hall
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.8.7 or,
+at your option, any later version of Perl 5 you may have available.
+
+=head1 FUNCTIONS
+
+=head2
+
+  copy_field( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName[, $regex[, $n ] ] );
+
+  Copies a value from one field to another. If a regular expression ( $regex ) is supplied,
+  the value will be transformed by the given regex before being copied into the new field.
+  Example: $regex = 's/Old Text/Replacement Text/'
+
+  If $n is passed, copy_field will only copy the Nth field of the list of fields.
+  E.g. $n = 1 will only use the first field's value, $n = 2 will use only the 2nd field's value.
+
+=cut
+
+sub copy_field {
+  my ( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName, $regex, $n ) = @_;
+  C4::Koha::Log( "C4::SimpleMARC::copy_field( '$record', '$fromFieldName', '$fromSubfieldName', '$toFieldName', '$toSubfieldName', '$regex', '$n' )" ) if $debug;
+
+  if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
+
+  my @values = read_field( $record, $fromFieldName, $fromSubfieldName );
+  @values = ( $values[$n-1] ) if ( $n );
+  C4::Koha::Log( "@values = read_field( $record, $fromFieldName, $fromSubfieldName )" ) if $debug >= 3;
+
+  if ( $regex ) {
+    foreach my $value ( @values ) {
+      C4::Koha::Log( "\$value =~ s$regex" ) if ( $debug >= 3 );
+      eval "\$value =~ s$regex";
+    }
+  }
+
+  update_field( $record, $toFieldName, $toSubfieldName, @values );
+
+}
+
+=head2
+
+  update_field( $record, $fieldName, $subfieldName, $value[, $value,[ $value ... ] ] );
+
+  Updates a field with the given value, creating it if neccessary.
+
+  If multiple values are supplied, they will be used to update a list of repeatable fields
+  until either the fields or the values are all used.
+
+  If a single value is supplied for a repeated field, that value will be used to update
+  each of the repeated fields.
+
+=cut
+
+sub update_field {
+  my ( $record, $fieldName, $subfieldName, @values ) = @_;
+  C4::Koha::Log( "C4::SimpleMARC::update_field( $record, $fieldName, $subfieldName, @values )" ) if $debug;
+
+  if ( ! ( $record && $fieldName ) ) { return; }
+
+  if ( @values eq 1 ) {
+    _update_repeatable_field_with_single_value( $record, $fieldName, $subfieldName, @values );
+    return;
+  }
+
+  my $i = 0;
+  my $field;
+  if ( $subfieldName ) {
+    if ( my @fields = $record->field( $fieldName ) ) {
+      foreach my $field ( @fields ) {
+        $field->update( "$subfieldName" => $values[$i++] );
+      }
+    } else {
+      ## Field does not exist, create it.
+      foreach my $value ( @values ) {
+        $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $values[$i++] );
+        $record->append_fields( $field );
+      }
+    }
+  } else { ## No subfield
+    if ( my @fields = $record->field( $fieldName ) ) {
+      foreach my $field ( @fields ) {
+        $field->update( $values[$i++] );
+      }
+    } else {
+      ## Field does not exists, create it
+      foreach my $value ( @values ) {
+        $field = MARC::Field->new( $fieldName, $value );
+        $record->append_fields( $field );
+      }
+    }
+  }
+}
+
+=head2
+
+  my @values = read_field( $record, $fieldName[, $subfieldName, [, $n ] ] );
+
+  Returns an array of field values for the given field and subfield
+
+  If $n is given, it will return only the $nth value of the array.
+  E.g. If $n = 1, it return the 1st value, if $n = 3, it will return the 3rd value.
+
+=cut
+
+sub read_field {
+  my ( $record, $fieldName, $subfieldName, $n ) = @_;
+  C4::Koha::Log( "C4::SimpleMARC::read_field( '$record', '$fieldName', '$subfieldName', '$n' )" ) if $debug;
+
+  my @fields = $record->field( $fieldName );
+
+  return @fields unless $subfieldName;
+
+  my @subfields;
+  foreach my $field ( @fields ) {
+    my @sf = $field->subfield( $subfieldName );
+    push( @subfields, @sf );
+  }
+
+  if ( $n ) {
+    return $subfields[$n-1];
+  } else {
+    return @subfields;
+  }
+}
+
+=head2
+
+  $bool = field_exists( $record, $fieldName[, $subfieldName ]);
+
+  Returns true if the field exits, false otherwise.
+
+=cut
+
+sub field_exists {
+  my ( $record, $fieldName, $subfieldName ) = @_;
+  C4::Koha::Log( "C4::SimpleMARC::field_exists( $record, $fieldName, $subfieldName )" ) if $debug;
+
+  if ( ! $record ) { return; }
+
+  my $return = 0;
+  if ( $fieldName && $subfieldName ) {
+    $return = $record->field( $fieldName ) && $record->subfield( $fieldName, $subfieldName );
+  } elsif ( $fieldName ) {
+    $return = $record->field( $fieldName ) && 1;
+  }
+
+  C4::Koha::Log( "C4:SimpleMARC::field_exists: Returning '$return'" ) if $debug >= 2;
+  return $return;
+}
+
+=head2
+
+  $bool = field_equals( $record, $value, $fieldName[, $subfieldName[, $regex [, $n ] ] ]);
+
+  Returns true if the field equals the given value, false otherwise.
+
+  If a regular expression ( $regex ) is supplied, the value will be compared using
+  the given regex. Example: $regex = 'm/sought_text/'
+
+  If $n is passed, the Nth field of a repeatable series will be used for comparison.
+  Set $n to 1 or leave empty for a non-repeatable field.
+
+=cut
+
+sub field_equals {
+  my ( $record, $value, $fieldName, $subfieldName, $regex, $n ) = @_;
+  $n = 1 unless ( $n ); ## $n defaults to first field of a repeatable field series
+  C4::Koha::Log( "C4::SimpleMARC::field_equals( '$record', '$value', '$fieldName', '$subfieldName', '$regex', '$n')" ) if $debug;
+
+  if ( ! $record ) { return; }
+
+  my @field_values = read_field( $record, $fieldName, $subfieldName, $n );
+  my $field_value = $field_values[$n-1];
+
+  if ( $regex ) {
+    C4::Koha::Log( "Testing '$field_value' =~ m$value" ) if $debug >= 3;
+    return eval "\$field_value =~ m$value";
+  } else {
+    return $field_value eq $value;
+  }
+}
+
+=head2
+
+  move_field( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName[, $regex [, $n ] ] );
+
+  Moves a value from one field to another. If a regular expression ( $regex ) is supplied,
+  the value will be transformed by the given regex before being moved into the new field.
+  Example: $regex = 's/Old Text/Replacement Text/'
+
+  If $n is passed, only the Nth field will be moved. $n = 1
+  will move the first repeatable field, $n = 3 will move the third.
+
+=cut
+
+sub move_field {
+  my ( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName, $regex, $n ) = @_;
+  C4::Koha::Log( "C4::SimpleMARC::move_field( '$record', '$fromFieldName', '$fromSubfieldName', '$toFieldName', '$toSubfieldName', '$regex', '$n' )" ) if $debug;
+  copy_field( $record, $fromFieldName, $fromSubfieldName, $toFieldName, $toSubfieldName, $regex, $n );
+  delete_field( $record, $fromFieldName, $fromSubfieldName, $n );
+}
+
+=head2
+
+  delete_field( $record, $fieldName[, $subfieldName [, $n ] ] );
+
+  Deletes the given field.
+
+  If $n is passed, only the Nth field will be deleted. $n = 1
+  will delete the first repeatable field, $n = 3 will delete the third.
+
+=cut
+
+sub delete_field {
+  my ( $record, $fieldName, $subfieldName, $n ) = @_;
+  C4::Koha::Log( "C4::SimpleMARC::delete_field( '$record', '$fieldName', '$subfieldName', '$n' )" ) if $debug;
+
+  my @fields = $record->field( $fieldName );
+
+  @fields = ( $fields[$n-1] ) if ( $n );
+
+  if ( @fields && !$subfieldName ) {
+    foreach my $field ( @fields ) {
+      $record->delete_field( $field );
+    }
+  } elsif ( @fields && $subfieldName ) {
+    foreach my $field ( @fields ) {
+      $field->delete_subfield( code => $subfieldName );
+    }
+  }
+}
+
+=head2
+
+  _update_repeatable_field_with_single_value( $record, $fieldName, $subfieldName, $value );
+
+  Updates a repeatable field, giving all existing copies of that field the given value.
+
+  This is an internal function, and thus is not exported.
+
+=cut
+
+sub _update_repeatable_field_with_single_value {
+  my ( $record, $fieldName, $subfieldName, $value ) = @_;
+  C4::Koha::Log( "C4::SimpleMARC::_update_repeatable_field_with_single_value( $record, $fieldName, $subfieldName, $value )" ) if $debug;
+
+  if ( ! ( $record && $fieldName ) ) { return; }
+
+  my $field;
+  if ( $subfieldName ) {
+    if ( my @fields = $record->field( $fieldName ) ) {
+      foreach my $field ( @fields ) {
+        $field->update( "$subfieldName" => $value );
+      }
+    } else {
+      ## Field does not exist, create it.
+      $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $value );
+      $record->append_fields( $field );
+    }
+  } else { ## No subfield
+    if ( my @fields = $record->field( $fieldName ) ) {
+      foreach my $field ( @fields ) {
+        $field->update( $value );
+      }
+    } else {
+      ## Field does not exists, create it
+      $field = MARC::Field->new( $fieldName, $value );
+      $record->append_fields( $field );
+    }
+  }
+}
+
+1;
+__END__
index 50387c5..7874c9e 100644 (file)
@@ -45,6 +45,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (13, 'rotating_collections', 'Manage rotating collections'),
    (13, 'upload_local_cover_images', 'Upload local cover images'),
    (13, 'manage_patron_lists', 'Add, edit and delete patron lists and their contents'),
+   (13, 'marc_modification_templates', 'Manage marc modification templates'),
    (15, 'check_expiration', 'Check the expiration of a serial'),
    (15, 'claim_serials', 'Claim missing serials'),
    (15, 'create_subscription', 'Create a new subscription'),
index 46814a9..f337a97 100644 (file)
@@ -3348,6 +3348,41 @@ ALTER TABLE `patron_list_patrons`
   ADD CONSTRAINT patron_list_patrons_ibfk_1 FOREIGN KEY (patron_list_id) REFERENCES patron_lists (patron_list_id) ON DELETE CASCADE ON UPDATE CASCADE,
   ADD CONSTRAINT patron_list_patrons_ibfk_2 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE;
 
+--
+-- Table structure for table 'marc_modification_templates'
+--
+
+CREATE TABLE IF NOT EXISTS marc_modification_templates (
+    template_id int(11) NOT NULL AUTO_INCREMENT,
+    name text NOT NULL,
+    PRIMARY KEY (template_id)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
+
+--
+-- Table structure for table 'marc_modification_template_actions'
+--
+
+CREATE TABLE IF NOT EXISTS marc_modification_template_actions (
+  mmta_id int(11) NOT NULL AUTO_INCREMENT,
+  template_id int(11) NOT NULL,
+  ordering int(3) NOT NULL,
+  action enum('delete_field','update_field','move_field','copy_field') NOT NULL,
+  field_number smallint(6) NOT NULL DEFAULT '0',
+  from_field varchar(3) NOT NULL,
+  from_subfield varchar(1) DEFAULT NULL,
+  field_value varchar(100) DEFAULT NULL,
+  to_field varchar(3) DEFAULT NULL,
+  to_subfield varchar(1) DEFAULT NULL,
+  to_regex text,
+  conditional enum('if','unless') DEFAULT NULL,
+  conditional_field varchar(3) DEFAULT NULL,
+  conditional_subfield varchar(1) DEFAULT NULL,
+  conditional_comparison enum('exists','not_exists','equals','not_equals') DEFAULT NULL,
+  conditional_value text,
+  conditional_regex tinyint(1) NOT NULL DEFAULT '0',
+  description text,
+  PRIMARY KEY (mmta_id)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
index 721a9d1..1af6a0f 100755 (executable)
@@ -7686,6 +7686,45 @@ if ( CheckVersion($DBversion) ) {
     SetVersion($DBversion);
 }
 
+$DBversion = "3.11.00.XXX";
+if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
+    $dbh->do("CREATE TABLE IF NOT EXISTS marc_modification_templates (
+              template_id int(11) NOT NULL auto_increment,
+              name text NOT NULL,
+              PRIMARY KEY  (template_id)
+              ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;"
+    );
+
+    $dbh->do("
+      CREATE TABLE IF NOT EXISTS marc_modification_template_actions (
+      mmta_id int(11) NOT NULL auto_increment,
+      template_id int(11) NOT NULL,
+      ordering int(3) NOT NULL,
+      action enum('delete_field','update_field','move_field','copy_field') NOT NULL,
+      field_number smallint(6) NOT NULL default '0',
+      from_field varchar(3) NOT NULL,
+      from_subfield varchar(1) NULL,
+      field_value varchar(100) default NULL,
+      to_field varchar(3) default NULL,
+      to_subfield varchar(1) default NULL,
+      to_regex text,
+      conditional enum('if','unless') default NULL,
+      conditional_field varchar(3) default NULL,
+      conditional_subfield varchar(1) default NULL,
+      conditional_comparison enum('exists','not_exists','equals','not_equals') default NULL,
+      conditional_value text,
+      conditional_regex tinyint(1) NOT NULL default '0',
+      description text,
+      PRIMARY KEY  (mmta_id)
+      ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
+    ");
+
+    $dbh->do("INSERT INTO permissions (module_bit, code, description) VALUES ('13', 'marc_modification_templates', 'Manage marc modification templates')");
+
+    print "Upgrade to $DBversion done ( Added tables for MARC Modification Framework )\n";
+    SetVersion($DBversion);
+}
+
 =head1 FUNCTIONS
 
 =head2 TableExists($table)
index dce18d5..871803e 100644 (file)
@@ -71,6 +71,9 @@
     <li><a href="/cgi-bin/koha/rotating_collections/rotatingCollections.pl">Rotating collections</a></li>
     [% END %]
 -->
+    [% IF ( CAN_user_tools_marc_modification_templates ) %]
+       <li><a href="/cgi-bin/koha/tools/marc_modification_templates.pl">Manage MARC modification templates</a></li>
+    [% END %]
     [% IF ( CAN_user_tools_stage_marc_import ) %]
        <li><a href="/cgi-bin/koha/tools/stage-marc-import.pl">Stage MARC for import</a></li>
     [% END %]
index a5dbe44..46a3b3a 100644 (file)
@@ -68,8 +68,8 @@
        <dd>Create and manage Bibliographic frameworks that define the characteristics of your MARC Records (field and subfield definitions) as well as templates for the MARC editor.</dd>
        <dt><a href="/cgi-bin/koha/admin/koha2marclinks.pl">Koha to MARC mapping</a></dt>
        <dd>Define the mapping between the Koha transactional database (SQL) and the MARC Bibliographic records. Note that the mapping can be defined through MARC Bibliographic Framework. This tool is just a shortcut to speed up linkage.</dd>
-       <dt><a href="/cgi-bin/koha/admin/fieldmapping.pl">Keywords to MARC mapping</a></dt>
-       <dd>Define the mapping between keywords and MARC fields, those keywords are used to find some datas independently of the framework.</dd>
+        <dt><a href="/cgi-bin/koha/admin/fieldmapping.pl">Keywords to MARC mapping</a></dt>
+        <dd>Define the mapping between keywords and MARC fields, those keywords are used to find some datas independently of the framework.</dd>
        <dt><a href="/cgi-bin/koha/admin/checkmarc.pl">MARC Bibliographic framework test</a></dt>
        <dd>Checks the MARC structure. If you change your MARC Bibliographic framework it's recommended that you run this tool to test for errors in your definition.</dd>
     <dt><a href="/cgi-bin/koha/admin/authtypes.pl">Authority types</a></dt>
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
new file mode 100644 (file)
index 0000000..255310d
--- /dev/null
@@ -0,0 +1,492 @@
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Tools &rsaquo; MARC Modification Templates</title>
+[% INCLUDE 'doc-head-close.inc' %]
+
+<script type="text/javascript">
+//<![CDATA[
+$(document).ready(function() {
+        $('#select_template').find("input:submit").hide();
+        $('#select_template').change(function() {
+                $('#select_template').submit();
+        });
+});
+//]]>
+</script>
+
+<script>
+function onActionChange(selectObj) {
+       // get the index of the selected option
+       var idx = selectObj.selectedIndex;
+
+       // get the value of the selected option
+       var action = selectObj.options[idx].value;
+
+       switch( action ) {
+               case 'delete_field':
+                       show('field_number_block');
+                       hide('with_value_block');
+                       hide('to_field_block');
+                       break;
+
+               case 'update_field':
+                       hide('field_number_block');
+                       show('with_value_block');
+                       hide('to_field_block');
+                       break;
+
+               case 'move_field':
+                       show('field_number_block');
+                       hide('with_value_block');
+                       show('to_field_block');
+                       break;
+
+               case 'copy_field':
+                       show('field_number_block');
+                       hide('with_value_block');
+                       show('to_field_block');
+                       break;
+
+       }
+}
+
+function onConditionalChange(selectObj) {
+        // get the index of the selected option
+        var idx = selectObj.selectedIndex;
+
+        // get the value of the selected option
+        var action = selectObj.options[idx].value;
+
+        switch( action ) {
+                case '':
+                        hide('conditional_block');
+                        break;
+
+                case 'if':
+                case 'unless':
+                        show('conditional_block');
+                        break;
+        }
+}
+
+function onConditionalComparisonChange(selectObj) {
+        // get the index of the selected option
+        var idx = selectObj.selectedIndex;
+
+        // get the value of the selected option
+        var action = selectObj.options[idx].value;
+
+        switch( action ) {
+                case 'equals':
+                case 'not_equals':
+                        show('conditional_comparison_block');
+                        break;
+
+               default:
+                        hide('conditional_comparison_block');
+                        break;
+        }
+}
+
+function onToFieldRegexChange( checkboxObj ) {
+       if ( checkboxObj.checked ) {
+               show('to_field_regex_value_block');
+       } else {
+               hide('to_field_regex_value_block');
+       }
+}
+
+function onConditionalRegexChange( checkboxObj ) {
+        if ( checkboxObj.checked ) {
+               document.getElementById( 'match_regex_prefix' ).style.visibility='visible';
+        } else {
+               document.getElementById( 'match_regex_prefix' ).style.visibility='hidden';
+        }
+}
+
+function show(eltId) {
+       elt = document.getElementById( eltId );
+       elt.style.display='inline';
+}
+
+function hide(eltId) {
+       clearFormElements( eltId );
+       elt = document.getElementById( eltId );
+       elt.style.display='none';
+}
+
+function clearFormElements(divId) {
+       myBlock = document.getElementById( divId );
+
+       var inputElements = myBlock.getElementsByTagName( "input" );
+       for (var i = 0; i < inputElements.length; i++) {
+               switch( inputElements[i].type ) {
+                       case "text":
+                               inputElements[i].value = '';
+                               break;
+                       case "checkbox":
+                               inputElements[i].checked = false;
+                               break;
+               }
+       }
+
+       var selectElements = myBlock.getElementsByTagName( "select" );
+       for (var i = 0; i < selectElements.length; i++) {
+               selectElements[i].selectedIndex = 0;
+       }
+
+}
+
+function confirmDelete() {
+       var agree = confirm("Are you sure you wish to delete this template?");
+       return agree;
+}
+
+var modaction_legend_innerhtml;
+var action_submit_value;
+
+function editAction( mmta_id, ordering, action, field_number, from_field, from_subfield, field_value, to_field,
+       to_subfield, to_regex, conditional, conditional_field, conditional_subfield,
+       conditional_comparison, conditional_value, conditional_regex, description
+) {
+       document.getElementById('mmta_id').value = mmta_id;
+
+       setSelectByValue( 'action', action );
+       document.getElementById('action').onchange();
+
+       setSelectByValue( 'field_number', field_number );
+
+       document.getElementById('from_field').value = from_field;
+       document.getElementById('from_subfield').value = from_subfield;
+       document.getElementById('field_value').value = field_value;
+       document.getElementById('to_field').value = to_field;
+       document.getElementById('to_subfield').value = to_subfield;
+       document.getElementById('to_regex').value = to_regex;
+
+       document.getElementById('to_field_regex').checked = to_regex.length;
+       document.getElementById('to_field_regex').onchange();
+
+       setSelectByValue( 'conditional', conditional );
+       document.getElementById('conditional').onchange();
+
+       document.getElementById('conditional_field').value = conditional_field;
+       document.getElementById('conditional_subfield').value = conditional_subfield;
+
+       setSelectByValue( 'conditional_comparison', conditional_comparison );
+       document.getElementById('conditional_comparison').onchange();
+
+       document.getElementById('conditional_value').value = conditional_value;
+
+       document.getElementById('conditional_regex').checked = parseInt( conditional_regex );
+
+       document.getElementById('description').value = description;
+
+       window.modaction_legend_innerhtml = document.getElementById('modaction_legend').innerhtml;
+       document.getElementById('modaction_legend').innerhtml = "Edit Action " + ordering;
+
+       window.action_submit_value = document.getElementById('action_submit').value;
+       document.getElementById('action_submit').value = "Update Action";
+
+       show('cancel_edit');
+}
+
+function cancelEditAction() {
+       document.getElementById('mmta_id').value = '';
+
+       setSelectByValue( 'action', 'delete_field' );
+       document.getElementById('action').onchange();
+
+       document.getElementById('from_field').value = '';
+       document.getElementById('from_subfield').value = '';
+       document.getElementById('field_value').value = '';
+       document.getElementById('to_field').value = '';
+       document.getElementById('to_subfield').value = '';
+       document.getElementById('to_regex').value = '';
+
+       document.getElementById('to_field_regex').checked = false;
+       document.getElementById('to_field_regex').onchange();
+
+       setSelectByValue( 'conditional', '' );
+       document.getElementById('conditional').onchange();
+
+       document.getElementById('conditional_field').value = '';
+       document.getElementById('conditional_subfield').value = '';
+
+       setSelectByValue( 'conditional_comparison', '' );
+       document.getElementById('conditional_comparison').onchange();
+
+       document.getElementById('conditional_value').value = '';
+
+       document.getElementById('conditional_regex').checked = false;
+
+       document.getElementById('modaction_legend').innerhtml = window.modaction_legend_innerhtml;
+       document.getElementById('action_submit').value = window.action_submit_value;
+
+       hide('cancel_edit');
+}
+
+function setSelectByValue( selectId, value ) {
+       s = document.getElementById( selectId );
+
+       for ( i = 0; i < s.options.length; i++ ) {
+               if ( s.options[i].value == value ) {
+                       s.selectedIndex = i;
+               }
+       }
+}
+
+</script>
+
+</head>
+
+<body>
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'cat-search.inc' %]
+
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; MARC Modification Templates</div>
+
+<div id="doc3" class="yui-t2">
+       <div id="yui-main">
+               <div class="yui-b">
+                       <h2>MARC Modification Templates</h2>
+
+                       [% IF ( TemplatesLoop ) %]
+
+                               <form method="get" action="/cgi-bin/koha/tools/marc_modification_templates.pl" id="select_template">
+                                       <label for="select_template">Template: </label>
+                                       <select name="template_id" id="select_template" style="width:20em;">
+                                               [% FOREACH TemplatesLoo IN TemplatesLoop %]
+                                                       <option value="[% TemplatesLoo.template_id %]" [% IF ( TemplatesLoo.selected ) %] selected [% END %] > [% TemplatesLoo.name %]</option>
+                                               [% END %]
+                                       </select>
+                                       <input type="hidden" name="op" value="select_template">
+                                       <input type="submit" value="Go" />
+                               </form>
+
+                               <form method="get" action="/cgi-bin/koha/tools/marc_modification_templates.pl" id="delete_template">
+                                       <input type="hidden" name="template_id" value="[% template_id %]" />
+                                       <input type="hidden" name="op" value="delete_template">
+                                       <input type="submit" value="Delete Template" onClick="return confirmDelete()" />
+                               </form>
+
+
+                               [% IF ( ActionsLoop ) %]
+                                       <table>
+                                               <caption>Actions for this template</caption>
+
+                                               <tr>
+                                                       <th>Change Order</th>
+                                                       <th>Order</th>
+                                                       <th>Action</th>
+                                                       <th>Description</th>
+                                                       <th>&nbsp</th>
+                                                       <th>&nbsp</th>
+                                               </tr>
+
+                                               [% FOREACH ActionsLoo IN ActionsLoop %]
+                                                       <tr>
+        <td style="white-space:nowrap;">
+               <a title="Move Action Up" href="marc_modification_templates.pl?op=move_action&amp;where=up&amp;template_id=[% ActionsLoo.template_id %]&amp;mmta_id=[% ActionsLoo.mmta_id %]">
+                       <img src="/intranet-tmpl/[% theme %]/img/go-up.png" border="0" alt="Go up" />
+                </a>
+
+               <a title="Move Action To Top" href="marc_modification_templates.pl?op=move_action&amp;where=top&amp;template_id=[% ActionsLoo.template_id %]&amp;mmta_id=[% ActionsLoo.mmta_id %]">
+                       <img src="/intranet-tmpl/[% theme %]/img/go-top.png" border="0" alt="Go top" />
+                </a>
+
+                <a title="Move Action To Bottom" href="marc_modification_templates.pl?op=move_action&amp;where=bottom&amp;template_id=[% ActionsLoo.template_id %]&amp;mmta_id=[% ActionsLoo.mmta_id %]">
+                       <img src="/intranet-tmpl/[% theme %]/img/go-bottom.png" border="0" alt="Go bottom" />
+                </a>
+
+                <a title="Move Action Down" href="marc_modification_templates.pl?op=move_action&amp;where=down&amp;template_id=[% ActionsLoo.template_id %]&amp;mmta_id=[% ActionsLoo.mmta_id %]">
+                       <img src="/intranet-tmpl/[% theme %]/img/go-down.png" border="0" alt="Go down" />
+                </a>
+        </td>
+
+                                                               <td>[% ActionsLoo.ordering %]</td>
+                                                               <td>
+                                                                       [% IF ( ActionsLoo.action_delete_field ) %] Delete [% END %]
+                                                                       [% IF ( ActionsLoo.action_update_field ) %] Update [% END %]
+                                                                       [% IF ( ActionsLoo.action_move_field ) %] Move [% END %]
+                                                                       [% IF ( ActionsLoo.action_copy_field ) %] Copy [% END %]
+
+                                                                       [% UNLESS ( ActionsLoo.action_update_field ) %]
+                                                                               [% IF ( ActionsLoo.field_number ) %]
+                                                                                       1st
+                                                                               [% END %]
+                                                                       [% END %]
+
+                                                                       field
+
+                                                                       [% ActionsLoo.from_field %][% IF ( ActionsLoo.from_subfield ) %]$[% ActionsLoo.from_subfield %][% END %]
+
+                                                                       [% IF ( ActionsLoo.field_value ) %]
+                                                                               with value <i>[% ActionsLoo.field_value %]</i>
+                                                                       [% END %]
+
+                                                                       [% IF ( ActionsLoo.to_field ) %]
+                                                                               to [% ActionsLoo.to_field %][% IF ( ActionsLoo.to_subfield ) %]$[% ActionsLoo.to_subfield %][% END %]
+
+                                                                               [% IF ( ActionsLoo.to_regex ) %]
+                                                                                        using RegEx s<strong>[% ActionsLoo.to_regex %]</strong>
+                                                                               [% END %]
+                                                                       [% END %]
+
+                                                                       [% IF ( ActionsLoo.conditional ) %]
+                                                                               [% IF ( ActionsLoo.conditional_if ) %] if [% END %]
+                                                                               [% IF ( ActionsLoo.conditional_unless ) %] unless [% END %]
+
+                                                                               [% ActionsLoo.conditional_field %][% IF ( ActionsLoo.conditional_subfield ) %]$[% ActionsLoo.conditional_subfield %][% END %]
+
+                                                                               [% IF ( ActionsLoo.conditional_comparison_exists ) %] exists [% END %]
+                                                                               [% IF ( ActionsLoo.conditional_comparison_not_exists ) %] does not exist [% END %]
+                                                                               [% IF ( ActionsLoo.conditional_comparison_equals ) %] matches [% END %]
+                                                                               [% IF ( ActionsLoo.conditional_comparison_not_equals ) %] does not match [% END %]
+
+                                                                               [% IF ( ActionsLoo.conditional_regex ) %] RegEx m[% END %]<strong>[% ActionsLoo.conditional_value %]</strong>
+                                                                       [% END %]
+                                                               </td>
+                                                               <td>[% ActionsLoo.description %]</td>
+                                                               <td><a href="#modaction" onclick='editAction(
+                                                                                                       "[% ActionsLoo.mmta_id |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.ordering |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.action |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.field_number |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.from_field |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.from_subfield |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.field_value |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.to_field |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.to_subfield |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.to_regex |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.conditional |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.conditional_field |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.conditional_subfield |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.conditional_comparison |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.conditional_value |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.conditional_regex |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]",
+                                                                                                       "[% ActionsLoo.description |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') %]"
+                                                                                               )'>Edit</a></td>
+                                                               <td><a href="marc_modification_templates.pl?template_id=[% ActionsLoo.template_id %]&op=delete_action&mmta_id=[% ActionsLoo.mmta_id %]">Delete</a></td>
+                                                       </tr>
+                                               [% END %]
+                                       </table>
+                               [% ELSE %]
+                                       <div class="dialog message"><p>There are no defined actions for this template.</p></div>
+                               [% END %]
+
+                               <form method="post" action="/cgi-bin/koha/tools/marc_modification_templates.pl" id="add_action" >
+                                       <a name="modaction"></a>
+                                       <fieldset>
+                                               <legend id="modaction_legend">Add A New Action</legend>
+
+                                               <select name="action" id="action" onchange="onActionChange(this);">
+                                                       <option value="delete_field">Delete</option>
+                                                       <option value="update_field">Add/Update</option>
+                                                       <option value="move_field">Move</option>
+                                                       <option value="copy_field">Copy</option>
+                                               </select>
+
+                                               <span id="field_number_block">
+                                                       <select name="field_number" id="field_number">
+                                                               <option value="0">All</option>
+                                                               <option value="1">1st</option>
+                                                       </select>
+                                               </span>
+
+                                               field(s) <input type="text" name="from_field" id="from_field" size="3" maxlength="3" /> <input type="text" name="from_subfield" id="from_subfield" size="1" maxlength="1" />
+
+                                               <span name="with_value_block" id="with_value_block" style="display:none;">
+                                                       with value <input type="text" name="field_value" id="field_value" />
+                                               </span>
+
+                                               <span name="to_field_block" id="to_field_block" style="display:none;">
+                                                       to field <input type="text" name="to_field" id="to_field" size="3" maxlength="3" /> <input type="text" name="to_subfield" id="to_subfield" size="1" maxlength="1" />
+
+                                                       <span name="to_field_regex_block" id="to_field_regex_block">
+                                                               <sup>
+                                                                       <label for="to_field_regex">RegEx</label>
+                                                                       <input type="checkbox" name="to_field_regex" id="to_field_regex" onchange="onToFieldRegexChange(this);" />
+
+                                                                       <span name="to_field_regex_value_block" id="to_field_regex_value_block" style="display:none;">
+                                                                               s<input type="text" name="to_regex" id="to_regex" />
+                                                                       </span>
+                                                               </sup>
+                                                       </span>
+                                               </span>
+
+                                               <p/>
+
+                                               <select name="conditional" id="conditional" onchange="onConditionalChange(this);">
+                                                       <option value="" selected />
+                                                       <option value="if">if</option>
+                                                       <option value="unless">unless</option>
+                                               </select>
+
+                                               <span name="conditional_block" id="conditional_block" style="display:none;">
+                                                       field <input type="text" name="conditional_field" id="conditional_field" size="3" maxlength="3" /> <input type="text" name="conditional_subfield" id="conditional_subfield" size="1" maxlength="1" />
+
+                                                       <select name="conditional_comparison" id="conditional_comparison" onchange="onConditionalComparisonChange(this);">
+                                                               <option value="" />
+                                                               <option value="exists">exists</option>
+                                                               <option value="not_exists">doesn't exist</option>
+                                                               <option value="equals">matches</option>
+                                                               <option value="not_equals">doesn't match</option>
+                                                       </select>
+
+                                                       <span name="conditional_comparison_block" id="conditional_comparison_block" style="display:none;">
+
+                                                               <span id="match_regex_prefix" style="visibility:hidden;">m</span><input type="text" id="conditional_value" name="conditional_value" />
+
+                                                               <sup>
+                                                                       <label for="conditional_regex">RegEx</label>
+                                                                       <input type="checkbox" name="conditional_regex" id="conditional_regex" onchange="onConditionalRegexChange(this);" />
+                                                               </sup>
+
+                                                       </span>
+                                               </span>
+
+                                               <input type="hidden" name="template_id" value="[% template_id %]" />
+                                               <input type="hidden" name="mmta_id" id="mmta_id" />
+                                               <input type="hidden" name="op" value="add_action" />
+
+                                               <br/><br/>
+                                               <label for="description">Description:</label>
+                                               <input type="text" name="description" id="description" size="60" />
+
+                                               <br/><br/>
+                                               <input id="action_submit" type="submit" value="Add Action" /> <a href="#modaction" id="cancel_edit" onclick="cancelEditAction();" style="display:none;">Cancel</a>
+
+                                       </fieldset>
+                               </form>
+
+                       [% ELSE %]
+                               <div class="dialog message"><p>There are no defined templates. Please create a template first.</p></div>
+                       [% END %]
+
+                       <form method="post" action="/cgi-bin/koha/tools/marc_modification_templates.pl" id="add_template" >
+                               <fieldset>
+                                       <legend>Create A New Template</legend>
+
+                                       <label for="template_name">Name: </label>
+                                       <input name="template_name" id="template_name" type="text" size="30" />
+
+                                       <input type="hidden" name="op" value="create_template" />
+                                       <input type="submit" value="Create Template" />
+
+                                       [% IF ( template_id ) %]
+                                               <input type="hidden" name="template_id" value="[% template_id %]" />
+                                               <input type="checkbox" name="duplicate_current_template" id="duplicate_current_template" />
+                                               <label for="duplicate_current_template">Duplicate Current Template</label>
+                                       [% END %]
+                               </fieldset>
+                       </form>
+
+
+
+               </div>
+       </div>
+
+       <div class="yui-b">
+               [% INCLUDE 'tools-menu.inc' %]
+       </div>
+</div>
+[% INCLUDE 'intranet-bottom.inc' %]
index 6c5613f..b9b17f5 100644 (file)
@@ -117,6 +117,23 @@ function CheckForm(f) {
             <select name="encoding" id="encoding"><option value="utf8" selected="selected">UTF-8 (Default)</option><option value="MARC-8">MARC 8</option><option value="ISO_5426">ISO 5426</option><option value="ISO_6937">ISO 6937</option><option value=ISO_8859-1">ISO 8859-1</option><option value="EUC-KR">EUC-KR</option></select>
        </li>
 </ol></fieldset>
+  [% IF MarcModificationTemplatesLoop %]
+    <fieldset class="rows">
+      <legend>Use MARC Modification Template:</legend>
+      <ol>
+        <li>
+          <label for="comments">Modify record using the following template: </label>
+          <select name="marc_modification_template_id" id="marc_modification_template_id">
+            <option value="">Do not use.</option>
+              [% FOREACH mmt IN MarcModificationTemplatesLoop %]
+                <option value="[% mmt.template_id %]">[% mmt.name %]</option>
+              [% END %]
+          </select>
+        </li>
+      </ol>
+    </fieldset>
+  [% END %]
+
   <fieldset class="rows">
     <legend>Look for existing records in catalog?</legend>
     <ol><li><label for="matcher">Record matching rule:</label>
index b842f57..8ae069f 100644 (file)
 <!--
     [% IF ( CAN_user_tools_rotating_collections ) %]
     <dt><a href="/cgi-bin/koha/rotating_collections/rotatingCollections.pl">Rotating collections</a></dt>
-       <dd>Manage Rotating Collections</dd>
+    <dd>Manage Rotating Collections</dd>
     [% END %]
 -->
 
+    [% IF ( CAN_user_tools_marc_modification_templates ) %]
+    <dt><a href="/cgi-bin/koha/tools/marc_modification_templates.pl">MARC Modification Templates</a></dt>
+    <dd>Manage templates for modifying MARC records during import.</dd>
+    [% END %]
+
     [% IF ( CAN_user_tools_stage_marc_import ) %]
     <dt><a href="/cgi-bin/koha/tools/stage-marc-import.pl">Stage MARC records for import</a></dt>
     <dd>Stage MARC records into the reservoir.</dd>
diff --git a/tools/marc_modification_templates.pl b/tools/marc_modification_templates.pl
new file mode 100755 (executable)
index 0000000..60a28cf
--- /dev/null
@@ -0,0 +1,151 @@
+#!/usr/bin/perl
+# Copyright 2010 Kyle M Hall <kyle.m.hall@gmail.com>
+#
+# 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 2 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use strict;
+use warnings;
+
+use CGI;
+
+use C4::Auth;
+use C4::Koha;
+use C4::Output;
+use Koha::MarcModificationTemplates;
+
+my $cgi = new CGI;
+
+my $op = $cgi->param('op');
+my $template_id = $cgi->param('template_id');
+
+my ($template, $loggedinuser, $cookie)
+    = get_template_and_user({template_name => "tools/marc_modification_templates.tmpl",
+                            query => $cgi,
+                            type => "intranet",
+                            authnotrequired => 0,
+                            flagsrequired => { tools => 'marc_modfication_templates' },
+                            debug => 1,
+                            });
+
+if ( $op eq "create_template" ) {
+  $template_id = '' unless $cgi->param('duplicate_current_template');
+  $template_id = AddModificationTemplate( $cgi->param('template_name'), $template_id );
+
+} elsif ( $op eq "delete_template" ) {
+
+  DelModificationTemplate( $template_id );
+  $template_id = '';
+
+} elsif ( $op eq "add_action" ) {
+
+  my $mmta_id = $cgi->param('mmta_id');
+  my $action = $cgi->param('action');
+  my $field_number = $cgi->param('field_number');
+  my $from_field = $cgi->param('from_field');
+  my $from_subfield = $cgi->param('from_subfield');
+  my $field_value = $cgi->param('field_value');
+  my $to_field = $cgi->param('to_field');
+  my $to_subfield = $cgi->param('to_subfield');
+  my $to_regex = $cgi->param('to_regex');
+  my $conditional = $cgi->param('conditional');
+  my $conditional_field = $cgi->param('conditional_field');
+  my $conditional_subfield = $cgi->param('conditional_subfield');
+  my $conditional_comparison = $cgi->param('conditional_comparison');
+  my $conditional_value = $cgi->param('conditional_value');
+  my $conditional_regex = $cgi->param('conditional_regex') eq 'on';
+  my $description = $cgi->param('description');
+
+  unless ( $mmta_id ) {
+    AddModificationTemplateAction(
+      $template_id,
+      $action,
+      $field_number,
+      $from_field,
+      $from_subfield,
+      $field_value,
+      $to_field,
+      $to_subfield,
+      $to_regex,
+      $conditional,
+      $conditional_field,
+      $conditional_subfield,
+      $conditional_comparison,
+      $conditional_value,
+      $conditional_regex,
+      $description
+    );
+  } else {
+    ModModificationTemplateAction(
+      $mmta_id,
+      $action,
+      $field_number,
+      $from_field,
+      $from_subfield,
+      $field_value,
+      $to_field,
+      $to_subfield,
+      $to_regex,
+      $conditional,
+      $conditional_field,
+      $conditional_subfield,
+      $conditional_comparison,
+      $conditional_value,
+      $conditional_regex,
+      $description
+    );
+
+  }
+
+} elsif ( $op eq "delete_action" ) {
+  DelModificationTemplateAction( $cgi->param('mmta_id') );
+
+} elsif ( $op eq "move_action" ) {
+
+  MoveModificationTemplateAction( $cgi->param('mmta_id'), $cgi->param('where') );
+
+}
+
+my @templates = GetModificationTemplates( $template_id );
+
+unless ( $template_id ) {
+  $template_id = $templates[0]->{'template_id'};
+  @templates = GetModificationTemplates( $template_id );
+}
+
+my @actions = GetModificationTemplateActions( $template_id );
+foreach my $action ( @actions ) {
+  $action->{'action_delete_field'} = ( $action->{'action'} eq 'delete_field' );
+  $action->{'action_update_field'} = ( $action->{'action'} eq 'update_field' );
+  $action->{'action_move_field'} = ( $action->{'action'} eq 'move_field' );
+  $action->{'action_copy_field'} = ( $action->{'action'} eq 'copy_field' );
+
+  $action->{'conditional_if'} = ( $action->{'conditional'} eq 'if' );
+  $action->{'conditional_unless'} = ( $action->{'conditional'} eq 'unless' );
+
+  $action->{'conditional_comparison_exists'} = ( $action->{'conditional_comparison'} eq 'exists' );
+  $action->{'conditional_comparison_not_exists'} = ( $action->{'conditional_comparison'} eq 'not_exists' );
+  $action->{'conditional_comparison_equals'} = ( $action->{'conditional_comparison'} eq 'equals' );
+  $action->{'conditional_comparison_not_equals'} = ( $action->{'conditional_comparison'} eq 'not_equals' );
+}
+
+$template->param(
+  TemplatesLoop => \@templates,
+  ActionsLoop => \@actions,
+
+  template_id => $template_id,
+);
+
+output_html_with_http_headers $cgi, $cookie, $template->output;
index fbb5365..86e5bbc 100755 (executable)
@@ -41,6 +41,7 @@ use C4::ImportBatch;
 use C4::Matcher;
 use C4::UploadedFile;
 use C4::BackgroundJob;
+use Koha::MarcModificationTemplates;
 
 my $input = new CGI;
 my $dbh = C4::Context->dbh;
@@ -57,6 +58,8 @@ my $item_action = $input->param('item_action');
 my $comments = $input->param('comments');
 my $record_type = $input->param('record_type');
 my $encoding = $input->param('encoding');
+my $marc_modification_template = $input->param('marc_modification_template_id');
+
 my ($template, $loggedinuser, $cookie)
        = get_template_and_user({template_name => "tools/stage-marc-import.tmpl",
                                        query => $input,
@@ -131,7 +134,7 @@ if ($completedJobID) {
     }
 
     # FIXME branch code
-    my ($batch_id, $num_valid, $num_items, @import_errors) = BatchStageMarcRecords($record_type, $encoding, $marcrecord, $filename, $comments, '', $parse_items, 0, 50, staging_progress_callback($job, $dbh));
+    my ($batch_id, $num_valid, $num_items, @import_errors) = BatchStageMarcRecords($record_type, $encoding, $marcrecord, $filename, $marc_modification_template, $comments, '', $parse_items, 0, 50, staging_progress_callback($job, $dbh));
 
     $dbh->commit();
 
@@ -180,6 +183,8 @@ if ($completedJobID) {
                          matcher_code => $matcher_code,
                          import_batch_id => $batch_id
                         );
+
+
     }
 
 } else {
@@ -189,6 +194,10 @@ if ($completedJobID) {
     }
     my @matchers = C4::Matcher::GetMatcherList();
     $template->param(available_matchers => \@matchers);
+
+    my @templates = GetModificationTemplates();
+    $template->param( MarcModificationTemplatesLoop => \@templates );
+
 }
 
 output_html_with_http_headers $input, $cookie, $template->output;