Bug 31535: Fix warning - uninitialized value $mode in string ne (addbiblio.pl)
[srvgit] / Koha / SimpleMARC.pm
index f9adf40..6fd08b1 100644 (file)
@@ -2,32 +2,39 @@ package Koha::SimpleMARC;
 
 # Copyright 2009 Kyle M. Hall <kyle.m.hall@gmail.com>
 
-use Modern::Perl;
-
-#use MARC::Record;
-
-require Exporter;
-
-our @ISA = qw(Exporter);
-our %EXPORT_TAGS = ( 'all' => [ qw(
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
-) ] );
 
-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';
+use Modern::Perl;
 
-our $debug = 0;
+our (@ISA, @EXPORT_OK);
+BEGIN {
+    require Exporter;
+    our @ISA = qw(Exporter);
+
+    @EXPORT_OK = qw(
+      read_field
+      add_field
+      update_field
+      copy_field
+      copy_and_replace_field
+      move_field
+      delete_field
+      field_exists
+      field_equals
+    );
+}
 
 =head1 NAME
 
@@ -80,53 +87,40 @@ sub copy_field {
     my $toFieldName = $params->{to_field};
     my $toSubfieldName = $params->{to_subfield};
     my $regex = $params->{regex};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
 
     if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
 
 
-    if ( not $fromSubfieldName or $fromSubfieldName eq ''
-      or not $toSubfieldName or $toSubfieldName eq ''
-    ) {
-        _copy_field({
-            record => $record,
-            from_field => $fromFieldName,
-            to_field => $toFieldName,
-            regex => $regex,
-            n => $n
-        });
+    if (   not $fromSubfieldName
+        or $fromSubfieldName eq ''
+        or not $toSubfieldName
+        or $toSubfieldName eq '' ) {
+        _copy_move_field(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                to_field      => $toFieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'copy',
+            }
+        );
     } else {
-        _copy_subfield({
-            record => $record,
-            from_field => $fromFieldName,
-            from_subfield => $fromSubfieldName,
-            to_field => $toFieldName,
-            to_subfield => $toSubfieldName,
-            regex => $regex,
-            n => $n
-        });
+        _copy_move_subfield(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                from_subfield => $fromSubfieldName,
+                to_field      => $toFieldName,
+                to_subfield   => $toSubfieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'copy',
+            }
+        );
     }
-
 }
 
-sub _copy_field {
-    my ( $params ) = @_;
-    my $record = $params->{record};
-    my $fromFieldName = $params->{from_field};
-    my $toFieldName = $params->{to_field};
-    my $regex = $params->{regex};
-    my $n = $params->{n};
-
-    _copy_move_field({
-        record => $record,
-        from_field => $fromFieldName,
-        to_field => $toFieldName,
-        regex => $regex,
-        n => $n
-    });
-}
-
-sub _copy_subfield {
+sub copy_and_replace_field {
     my ( $params ) = @_;
     my $record = $params->{record};
     my $fromFieldName = $params->{from_field};
@@ -134,13 +128,36 @@ sub _copy_subfield {
     my $toFieldName = $params->{to_field};
     my $toSubfieldName = $params->{to_subfield};
     my $regex = $params->{regex};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
 
-    my @values = read_field({ record => $record, field => $fromFieldName, subfield => $fromSubfieldName });
-    @values = ( $values[$n-1] ) if ( $n );
-    _modify_values({ values => \@values, regex => $regex });
+    if ( ! ( $record && $fromFieldName && $toFieldName ) ) { return; }
 
-    update_field({ record => $record, field => $toFieldName, subfield => $toSubfieldName, values => \@values });
+
+    if ( !defined $fromSubfieldName or $fromSubfieldName eq ''
+      or !defined $toSubfieldName or $toSubfieldName eq ''
+    ) {
+        _copy_move_field(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                to_field      => $toFieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'replace',
+            }
+        );
+    } else {
+        _copy_move_subfield(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                from_subfield => $fromSubfieldName,
+                to_field      => $toFieldName,
+                to_subfield   => $toSubfieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'replace',
+            }
+        );
+    }
 }
 
 sub update_field {
@@ -149,15 +166,55 @@ sub update_field {
     my $fieldName = $params->{field};
     my $subfieldName = $params->{subfield};
     my @values = @{ $params->{values} };
+    my $field_numbers = $params->{field_numbers} // [];
 
     if ( ! ( $record && $fieldName ) ) { return; }
 
-    if ( not $subfieldName or $subfieldName eq '' ) {
+    if ( not defined $subfieldName or $subfieldName eq '' ) {
         # FIXME I'm not sure the actual implementation is correct.
         die "This action is not implemented yet";
         #_update_field({ record => $record, field => $fieldName, values => \@values });
     } else {
-        _update_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, values => \@values });
+        _update_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, values => \@values, field_numbers => $field_numbers });
+    }
+}
+
+=head2 add_field
+
+  add_field({
+      record   => $record,
+      field    => $fieldName,
+      subfield => $subfieldName,
+      values   => \@values,
+      field_numbers => $field_numbers,
+  });
+
+  Adds a new field/subfield with supplied value(s).
+  This function always add a new field as opposed to 'update_field' which will
+  either update if field exists and add if it does not.
+
+=cut
+
+
+sub add_field {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fieldName = $params->{field};
+    my $subfieldName = $params->{subfield};
+    my @values = @{ $params->{values} };
+    my $field_numbers = $params->{field_numbers} // [];
+
+    if ( ! ( $record && $fieldName ) ) { return; }
+    if ( $fieldName > 10 ) {
+        foreach my $value ( @values ) {
+            my $field = MARC::Field->new( $fieldName, '', '', "$subfieldName" => $value );
+            $record->append_fields( $field );
+        }
+    } else {
+        foreach my $value ( @values ) {
+            my $field = MARC::Field->new( $fieldName, $value );
+            $record->append_fields( $field );
+        }
     }
 }
 
@@ -194,9 +251,16 @@ sub _update_subfield {
     my $subfieldName = $params->{subfield};
     my @values = @{ $params->{values} };
     my $dont_erase = $params->{dont_erase};
+    my $field_numbers = $params->{field_numbers} // [];
     my $i = 0;
 
-    if ( my @fields = $record->field( $fieldName ) ) {
+    my @fields = $record->field( $fieldName );
+
+    if ( @$field_numbers ) {
+        @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
+    }
+
+    if ( @fields ) {
         unless ( $dont_erase ) {
             @values = ($values[0]) x scalar( @fields )
                 if @values == 1;
@@ -236,12 +300,12 @@ sub read_field {
     my $record = $params->{record};
     my $fieldName = $params->{field};
     my $subfieldName = $params->{subfield};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
 
-    if ( not $subfieldName or $subfieldName eq '' ) {
-        _read_field({ record => $record, field => $fieldName, n => $n });
+    if ( not defined $subfieldName or $subfieldName eq '' ) {
+        _read_field({ record => $record, field => $fieldName, field_numbers => $field_numbers });
     } else {
-        _read_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, n => $n });
+        _read_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, field_numbers => $field_numbers });
     }
 }
 
@@ -249,7 +313,7 @@ sub _read_field {
     my ( $params ) = @_;
     my $record = $params->{record};
     my $fieldName = $params->{field};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
 
     my @fields = $record->field( $fieldName );
 
@@ -259,10 +323,12 @@ sub _read_field {
         if $fieldName < 10;
 
     my @values;
-    if ( $n ) {
-        if ( $n <= scalar( @fields ) ) {
-            for my $sf ( $fields[$n - 1]->subfields ) {
-                push @values, $sf->[1];
+    if ( @$field_numbers ) {
+        for my $field_number ( @$field_numbers ) {
+            if ( $field_number <= scalar( @fields ) ) {
+                for my $sf ( $fields[$field_number - 1]->subfields ) {
+                    push @values, $sf->[1];
+                }
             }
         }
     } else {
@@ -281,7 +347,7 @@ sub _read_subfield {
     my $record = $params->{record};
     my $fieldName = $params->{field};
     my $subfieldName = $params->{subfield};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
 
     my @fields = $record->field( $fieldName );
 
@@ -293,16 +359,18 @@ sub _read_subfield {
         push( @values, @sf );
     }
 
-    return $n
-        ? $values[$n-1]
-        : @values;
+    if ( @values and @$field_numbers ) {
+        @values = map { $_ <= @values ? $values[ $_ - 1 ] : () } @$field_numbers;
+    }
+
+    return @values;
 }
 
 =head2 field_exists
 
-  $bool = field_exists( $record, $fieldName[, $subfieldName ]);
+  @field_numbers = field_exists( $record, $fieldName[, $subfieldName ]);
 
-  Returns true if the field exits, false otherwise.
+  Returns the field numbers or an empty array.
 
 =cut
 
@@ -314,28 +382,30 @@ sub field_exists {
 
   if ( ! $record ) { return; }
 
-  my $return = 0;
-  if ( $fieldName && $subfieldName ) {
-    $return = $record->field( $fieldName ) && $record->subfield( $fieldName, $subfieldName );
-  } elsif ( $fieldName ) {
-    $return = $record->field( $fieldName ) && 1;
+  my @field_numbers = ();
+  my $current_field_number = 1;
+  for my $field ( $record->field( $fieldName ) ) {
+    if ( $subfieldName ) {
+      push @field_numbers, $current_field_number
+        if $field->subfield( $subfieldName );
+    } else {
+      push @field_numbers, $current_field_number;
+    }
+    $current_field_number++;
   }
 
-  return $return;
+  return \@field_numbers;
 }
 
 =head2 field_equals
 
-  $bool = field_equals( $record, $value, $fieldName[, $subfieldName[, $regex [, $n ] ] ]);
+  $bool = field_equals( $record, $value, $fieldName[, $subfieldName[, $regex ] ]);
 
   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 = '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 {
@@ -344,20 +414,39 @@ sub field_equals {
   my $value = $params->{value};
   my $fieldName = $params->{field};
   my $subfieldName = $params->{subfield};
-  my $regex = $params->{regex};
-  my $n = $params->{n};
-  $n = 1 unless ( $n ); ## $n defaults to first field of a repeatable field series
+  my $is_regex = $params->{is_regex};
 
   if ( ! $record ) { return; }
 
-  my @field_values = read_field({ record => $record, field => $fieldName, subfield => $subfieldName, n => $n });
-  my $field_value = $field_values[$n-1];
+  my @field_numbers = ();
+  my $current_field_number = 1;
+  FIELDS: for my $field ( $record->field( $fieldName ) ) {
+    my @subfield_values;
+    if ( $field->is_control_field ) {
+        push @subfield_values, $field->data;
+    } else {
+        @subfield_values =
+            $subfieldName
+          ? $field->subfield($subfieldName)
+          : map { $_->[1] } $field->subfields;
+    }
 
-  if ( $regex ) {
-    return $field_value =~ m/$value/;
-  } else {
-    return $field_value eq $value;
+    SUBFIELDS: for my $subfield_value ( @subfield_values ) {
+      if (
+          (
+              $is_regex and $subfield_value =~ m/$value/
+          ) or (
+              $subfield_value eq $value
+          )
+      ) {
+          push @field_numbers, $current_field_number;
+          last SUBFIELDS;
+      }
+    }
+    $current_field_number++;
   }
+
+  return \@field_numbers;
 }
 
 =head2 move_field
@@ -381,73 +470,36 @@ sub move_field {
     my $toFieldName = $params->{to_field};
     my $toSubfieldName = $params->{to_subfield};
     my $regex = $params->{regex};
-    my $n = $params->{n};
-
-    if ( not $fromSubfieldName or $fromSubfieldName eq ''
-        or not $toSubfieldName or $toSubfieldName eq ''
-    ) {
-        _move_field({
-            record => $record,
-            from_field => $fromFieldName,
-            to_field => $toFieldName,
-            regex => $regex,
-            n => $n,
-        });
+    my $field_numbers = $params->{field_numbers} // [];
+
+    if (   !defined $fromSubfieldName
+        or $fromSubfieldName eq ''
+        or !defined $toSubfieldName
+        or $toSubfieldName eq '' ) {
+        _copy_move_field(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                to_field      => $toFieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'move',
+            }
+        );
     } else {
-        _move_subfield({
-            record => $record,
-            from_field => $fromFieldName,
-            from_subfield => $fromSubfieldName,
-            to_field => $toFieldName,
-            to_subfield => $toSubfieldName,
-            regex => $regex,
-            n => $n,
-        });
+        _copy_move_subfield(
+            {   record        => $record,
+                from_field    => $fromFieldName,
+                from_subfield => $fromSubfieldName,
+                to_field      => $toFieldName,
+                to_subfield   => $toSubfieldName,
+                regex         => $regex,
+                field_numbers => $field_numbers,
+                action        => 'move',
+            }
+        );
     }
 }
 
-sub _move_field {
-    my ( $params ) = @_;
-    my $record = $params->{record};
-    my $fromFieldName = $params->{from_field};
-    my $toFieldName = $params->{to_field};
-    my $regex = $params->{regex};
-    my $n = $params->{n};
-    _copy_move_field({
-        record => $record,
-        from_field => $fromFieldName,
-        to_field => $toFieldName,
-        regex => $regex,
-        n => $n,
-        action => 'move',
-    });
-}
-
-sub _move_subfield {
-    my ( $params ) = @_;
-    my $record = $params->{record};
-    my $fromFieldName = $params->{from_field};
-    my $fromSubfieldName = $params->{from_subfield};
-    my $toFieldName = $params->{to_field};
-    my $toSubfieldName = $params->{to_subfield};
-    my $regex = $params->{regex};
-    my $n = $params->{n};
-
-    # Copy
-    my @values = read_field({ record => $record, field => $fromFieldName, subfield => $fromSubfieldName });
-    @values = ( $values[$n-1] ) if $n;
-    _modify_values({ values => \@values, regex => $regex });
-    _update_subfield({ record => $record, field => $toFieldName, subfield => $toSubfieldName, dont_erase => 1, values => \@values });
-
-    # And delete
-    _delete_subfield({
-        record => $record,
-        field => $fromFieldName,
-        subfield => $fromSubfieldName,
-        n => $n,
-    });
-}
-
 =head2 _delete_field
 
   _delete_field( $record, $fieldName[, $subfieldName [, $n ] ] );
@@ -464,12 +516,12 @@ sub delete_field {
     my $record = $params->{record};
     my $fieldName = $params->{field};
     my $subfieldName = $params->{subfield};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
 
-    if ( not $subfieldName or $subfieldName eq '' ) {
-        _delete_field({ record => $record, field => $fieldName, n => $n });
+    if ( !defined $subfieldName or $subfieldName eq '' ) {
+        _delete_field({ record => $record, field => $fieldName, field_numbers => $field_numbers });
     } else {
-        _delete_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, n => $n });
+        _delete_subfield({ record => $record, field => $fieldName, subfield => $subfieldName, field_numbers => $field_numbers });
     }
 }
 
@@ -477,11 +529,13 @@ sub _delete_field {
     my ( $params ) = @_;
     my $record = $params->{record};
     my $fieldName = $params->{field};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
 
     my @fields = $record->field( $fieldName );
 
-    @fields = ( $fields[$n-1] ) if ( $n );
+    if ( @$field_numbers ) {
+        @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
+    }
     foreach my $field ( @fields ) {
         $record->delete_field( $field );
     }
@@ -492,14 +546,17 @@ sub _delete_subfield {
     my $record = $params->{record};
     my $fieldName = $params->{field};
     my $subfieldName = $params->{subfield};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
 
     my @fields = $record->field( $fieldName );
 
-    @fields = ( $fields[$n-1] ) if ( $n );
+    if ( @$field_numbers ) {
+        @fields = map { $_ <= @fields ? $fields[ $_ - 1 ] : () } @$field_numbers;
+    }
 
     foreach my $field ( @fields ) {
         $field->delete_subfield( code => $subfieldName );
+        $record->delete_field( $field ) unless $field->subfields();
     }
 }
 
@@ -510,16 +567,17 @@ sub _copy_move_field {
     my $fromFieldName = $params->{from_field};
     my $toFieldName = $params->{to_field};
     my $regex = $params->{regex};
-    my $n = $params->{n};
+    my $field_numbers = $params->{field_numbers} // [];
     my $action = $params->{action} || 'copy';
 
-    my @fields = $record->field( $fromFieldName );
-    if ( $n and $n <= scalar( @fields ) ) {
-        @fields = ( $fields[$n - 1] );
+    my @from_fields = $record->field( $fromFieldName );
+    if ( @$field_numbers ) {
+        @from_fields = map { $_ <= @from_fields ? $from_fields[ $_ - 1 ] : () } @$field_numbers;
     }
 
-    for my $field ( @fields ) {
-        my $new_field = $field->clone;
+    my @new_fields;
+    for my $from_field ( @from_fields ) {
+        my $new_field = $from_field->clone;
         $new_field->{_tag} = $toFieldName; # Should be replaced by set_tag, introduced by MARC::Field 2.0.4
         if ( $regex and $regex->{search} ) {
             for my $subfield ( $new_field->subfields ) {
@@ -528,9 +586,47 @@ sub _copy_move_field {
                 $new_field->update( $subfield->[0], $value );
             }
         }
-        $record->append_fields( $new_field );
-        $record->delete_field( $field )
-            if $action eq 'move';
+        if ( $action eq 'move' ) {
+            $record->delete_field( $from_field )
+        }
+        elsif ( $action eq 'replace' ) {
+            my @to_fields = $record->field( $toFieldName );
+            if ( @to_fields ) {
+                $record->delete_field( $to_fields[0] );
+            }
+        }
+        push @new_fields, $new_field;
+    }
+    $record->append_fields( @new_fields );
+}
+
+sub _copy_move_subfield {
+    my ( $params ) = @_;
+    my $record = $params->{record};
+    my $fromFieldName = $params->{from_field};
+    my $fromSubfieldName = $params->{from_subfield};
+    my $toFieldName = $params->{to_field};
+    my $toSubfieldName = $params->{to_subfield};
+    my $regex = $params->{regex};
+    my $field_numbers = $params->{field_numbers} // [];
+    my $action = $params->{action} || 'copy';
+
+    my @values = read_field({ record => $record, field => $fromFieldName, subfield => $fromSubfieldName });
+    if ( @$field_numbers ) {
+        @values = map { $_ <= @values ? $values[ $_ - 1 ] : () } @$field_numbers;
+    }
+    _modify_values({ values => \@values, regex => $regex });
+    my $dont_erase = $action eq 'copy' ? 1 : 0;
+    _update_subfield({ record => $record, field => $toFieldName, subfield => $toSubfieldName, values => \@values, dont_erase => $dont_erase });
+
+    # And delete if it's a move
+    if ( $action eq 'move' ) {
+        _delete_subfield({
+            record => $record,
+            field => $fromFieldName,
+            subfield => $fromSubfieldName,
+            field_numbers => $field_numbers,
+        });
     }
 }
 
@@ -540,6 +636,9 @@ sub _modify_values {
     my $regex = $params->{regex};
 
     if ( $regex and $regex->{search} ) {
+        my $replace = $regex->{replace};
+        $replace =~ s/"/\\"/g;                    # Protection from embedded code
+        $replace = '"' . $replace . '"'; # Put in a string for /ee
         $regex->{modifiers} //= q||;
         my @available_modifiers = qw( i g );
         my $modifiers = q||;
@@ -549,16 +648,16 @@ sub _modify_values {
         }
         foreach my $value ( @$values ) {
             if ( $modifiers =~ m/^(ig|gi)$/ ) {
-                $value =~ s/$regex->{search}/$regex->{replace}/ig;
+                $value =~ s/$regex->{search}/$replace/igee;
             }
             elsif ( $modifiers eq 'i' ) {
-                $value =~ s/$regex->{search}/$regex->{replace}/i;
+                $value =~ s/$regex->{search}/$replace/iee;
             }
             elsif ( $modifiers eq 'g' ) {
-                $value =~ s/$regex->{search}/$regex->{replace}/g;
+                $value =~ s/$regex->{search}/$replace/gee;
             }
             else {
-                $value =~ s/$regex->{search}/$regex->{replace}/;
+                $value =~ s/$regex->{search}/$replace/ee;
             }
         }
     }