Bug 13167 Stage MARC for Import hangs for biblio containing invalid ISBN-13
[koha.git] / C4 / Koha.pm
index bb3efc7..ec65be9 100644 (file)
@@ -25,10 +25,12 @@ use strict;
 
 use C4::Context;
 use C4::Branch qw(GetBranchesCount);
+use Koha::Cache;
 use Koha::DateUtils qw(dt_from_string);
-use Memoize;
 use DateTime::Format::MySQL;
+use Business::ISBN;
 use autouse 'Data::Dumper' => qw(Dumper);
+use DBI qw(:sql_types);
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $DEBUG);
 
@@ -44,6 +46,7 @@ BEGIN {
                &GetSupportName &GetSupportList
                &get_itemtypeinfos_of
                &getframeworks &getframeworkinfo
+        &GetFrameworksLoop
                &getauthtypes &getauthtype
                &getallthemes
                &getFacets
@@ -70,15 +73,16 @@ BEGIN {
                &GetNormalizedOCLCNumber
         &xml_escape
 
+        &GetVariationsOfISBN
+        &GetVariationsOfISBNs
+        &NormalizeISBN
+
                $DEBUG
        );
        $DEBUG = 0;
 @EXPORT_OK = qw( GetDailyQuote );
 }
 
-# expensive functions
-memoize('GetAuthorisedValues');
-
 =head1 NAME
 
 C4::Koha - Perl Module containing convenience functions for Koha scripts
@@ -188,7 +192,7 @@ build a HTML select with the following code :
 
 sub GetSupportList{
        my $advanced_search_types = C4::Context->preference("AdvancedSearchTypes");
-       if (!$advanced_search_types or $advanced_search_types eq 'itemtypes') {  
+    if (!$advanced_search_types or $advanced_search_types =~ /itemtypes/) {
                my $query = qq|
                        SELECT *
                        FROM   itemtypes
@@ -357,12 +361,13 @@ build a HTML select with the following code :
 
 =head3 in PERL SCRIPT
 
-  my $frameworks = frameworks();
+  my $frameworks = getframeworks();
   my @frameworkloop;
   foreach my $thisframework (keys %$frameworks) {
     my $selected = 1 if $thisframework eq $frameworkcode;
-    my %row =(value => $thisframework,
-                selected => $selected,
+    my %row =(
+                value       => $thisframework,
+                selected    => $selected,
                 description => $frameworks->{$thisframework}->{'frameworktext'},
             );
     push @frameworksloop, \%row;
@@ -371,14 +376,18 @@ build a HTML select with the following code :
 
 =head3 in TEMPLATE
 
-  <form action='<!-- TMPL_VAR name="script_name" -->' method=post>
+  <form action="[% script_name %] method=post>
     <select name="frameworkcode">
         <option value="">Default</option>
-    <!-- TMPL_LOOP name="frameworkloop" -->
-        <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="frameworktext" --></option>
-    <!-- /TMPL_LOOP -->
+        [% FOREACH framework IN frameworkloop %]
+        [% IF ( framework.selected ) %]
+        <option value="[% framework.value %]" selected="selected">[% framework.description %]</option>
+        [% ELSE %]
+        <option value="[% framework.value %]">[% framework.description %]</option>
+        [% END %]
+        [% END %]
     </select>
-    <input type=text name=searchfield value="<!-- TMPL_VAR name="searchfield" -->">
+    <input type=text name=searchfield value="[% searchfield %]">
     <input type="submit" value="OK" class="button">
   </form>
 
@@ -397,6 +406,55 @@ sub getframeworks {
     return ( \%itemtypes );
 }
 
+=head2 GetFrameworksLoop
+
+  $frameworks = GetFrameworksLoop( $frameworkcode );
+
+Returns the loop suggested on getframework(), but ordered by framework description.
+
+build a HTML select with the following code :
+
+=head3 in PERL SCRIPT
+
+  $template->param( frameworkloop => GetFrameworksLoop( $frameworkcode ) );
+
+=head3 in TEMPLATE
+
+  Same as getframework()
+
+  <form action="[% script_name %] method=post>
+    <select name="frameworkcode">
+        <option value="">Default</option>
+        [% FOREACH framework IN frameworkloop %]
+        [% IF ( framework.selected ) %]
+        <option value="[% framework.value %]" selected="selected">[% framework.description %]</option>
+        [% ELSE %]
+        <option value="[% framework.value %]">[% framework.description %]</option>
+        [% END %]
+        [% END %]
+    </select>
+    <input type=text name=searchfield value="[% searchfield %]">
+    <input type="submit" value="OK" class="button">
+  </form>
+
+=cut
+
+sub GetFrameworksLoop {
+    my $frameworkcode = shift;
+    my $frameworks = getframeworks();
+    my @frameworkloop;
+    foreach my $thisframework (sort { uc($frameworks->{$a}->{'frameworktext'}) cmp uc($frameworks->{$b}->{'frameworktext'}) } keys %$frameworks) {
+        my $selected = ( $thisframework eq $frameworkcode ) ? 1 : undef;
+        my %row = (
+                value       => $thisframework,
+                selected    => $selected,
+                description => $frameworks->{$thisframework}->{'frameworktext'},
+            );
+        push @frameworkloop, \%row;
+  }
+  return \@frameworkloop;
+}
+
 =head2 getframeworkinfo
 
   $frameworkinfo = &getframeworkinfo($frameworkcode);
@@ -711,19 +769,40 @@ sub getFacets {
             {
                 idx  => 'location',
                 label => 'Location',
-                tags        => [ qw/ 995c / ],
+                tags        => [ qw/ 995e / ],
             }
             ];
 
-            my $library_facet;
-            unless ( C4::Context->preference("singleBranchMode") || GetBranchesCount() == 1 ) {
-                $library_facet = {
-                    idx  => 'branch',
-                    label => 'Libraries',
-                    tags        => [ qw/ 995b / ],
-                };
+            unless ( C4::Context->preference("singleBranchMode")
+                || GetBranchesCount() == 1 )
+            {
+                my $DisplayLibraryFacets = C4::Context->preference('DisplayLibraryFacets');
+                if (   $DisplayLibraryFacets eq 'both'
+                    || $DisplayLibraryFacets eq 'holding' )
+                {
+                    push(
+                        @$facets,
+                        {
+                            idx   => 'holdingbranch',
+                            label => 'HoldingLibrary',
+                            tags  => [qw / 995c /],
+                        }
+                    );
+                }
+
+                if (   $DisplayLibraryFacets eq 'both'
+                    || $DisplayLibraryFacets eq 'home' )
+                {
+                push(
+                    @$facets,
+                    {
+                        idx   => 'homebranch',
+                        label => 'HomeLibrary',
+                        tags  => [qw / 995b /],
+                    }
+                );
+                }
             }
-            push( @$facets, $library_facet );
     }
     else {
         $facets = [
@@ -776,15 +855,36 @@ sub getFacets {
             },
             ];
 
-            my $library_facet;
-            unless ( C4::Context->preference("singleBranchMode") || GetBranchesCount() == 1 ) {
-                $library_facet = {
-                    idx  => 'branch',
-                    label => 'Libraries',
-                    tags        => [ qw / 952b / ],
-                };
+            unless ( C4::Context->preference("singleBranchMode")
+                || GetBranchesCount() == 1 )
+            {
+                my $DisplayLibraryFacets = C4::Context->preference('DisplayLibraryFacets');
+                if (   $DisplayLibraryFacets eq 'both'
+                    || $DisplayLibraryFacets eq 'holding' )
+                {
+                    push(
+                        @$facets,
+                        {
+                            idx   => 'holdingbranch',
+                            label => 'HoldingLibrary',
+                            tags  => [qw / 952b /],
+                        }
+                    );
+                }
+
+                if (   $DisplayLibraryFacets eq 'both'
+                    || $DisplayLibraryFacets eq 'home' )
+                {
+                push(
+                    @$facets,
+                    {
+                        idx   => 'homebranch',
+                        label => 'HomeLibrary',
+                        tags  => [qw / 952a /],
+                    }
+                );
+                }
             }
-            push( @$facets, $library_facet );
     }
     return $facets;
 }
@@ -1023,13 +1123,33 @@ This function returns all authorised values from the'authorised_value' table in
 
 C<$category> returns authorised values for just one category (optional).
 
+C<$selected> adds a "selected => 1" entry to the hash if the
+authorised_value matches it. B<NOTE:> this feature should be considered
+deprecated as it may be removed in the future.
+
 C<$opac> If set to a true value, displays OPAC descriptions rather than normal ones when they exist.
 
 =cut
 
 sub GetAuthorisedValues {
     my ( $category, $selected, $opac ) = @_;
-    my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
+
+    # TODO: the "selected" feature should be replaced by a utility function
+    # somewhere else, it doesn't belong in here. For starters it makes
+    # caching much more complicated. Or just let the UI logic handle it, it's
+    # what it's for.
+
+    # Is this cached already?
+    $opac = $opac ? 1 : 0;    # normalise to be safe
+    my $branch_limit =
+      C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
+    my $selected_key = defined($selected) ? $selected : '';
+    my $cache_key =
+      "AuthorisedValues-$category-$selected_key-$opac-$branch_limit";
+    my $cache  = Koha::Cache->get_instance();
+    my $result = $cache->get_from_cache($cache_key);
+    return $result if $result;
+
     my @results;
     my $dbh      = C4::Context->dbh;
     my $query = qq{
@@ -1075,6 +1195,11 @@ sub GetAuthorisedValues {
         push @results, $data;
     }
     $sth->finish;
+
+    # We can't cache for long because of that "selected" thing which
+    # makes it impossible to clear the cache without iterating through every
+    # value, which sucks. This'll cover this request, and not a whole lot more.
+    $cache->set_in_cache( $cache_key, \@results, { deepcopy => 1, expiry => 5 } );
     return \@results;
 }
 
@@ -1391,6 +1516,44 @@ sub GetNormalizedOCLCNumber {
     }
 }
 
+sub GetAuthvalueDropbox {
+    my ( $authcat, $default ) = @_;
+    my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
+    my $dbh = C4::Context->dbh;
+
+    my $query = qq{
+        SELECT *
+        FROM authorised_values
+    };
+    $query .= qq{
+          LEFT JOIN authorised_values_branches ON ( id = av_id )
+    } if $branch_limit;
+    $query .= qq{
+        WHERE category = ?
+    };
+    $query .= " AND ( branchcode = ? OR branchcode IS NULL )" if $branch_limit;
+    $query .= " GROUP BY lib ORDER BY category, lib, lib_opac";
+    my $sth = $dbh->prepare($query);
+    $sth->execute( $authcat, $branch_limit ? $branch_limit : () );
+
+
+    my $option_list = [];
+    my @authorised_values = ( q{} );
+    while (my $av = $sth->fetchrow_hashref) {
+        push @{$option_list}, {
+            value => $av->{authorised_value},
+            label => $av->{lib},
+            default => ($default eq $av->{authorised_value}),
+        };
+    }
+
+    if ( @{$option_list} ) {
+        return $option_list;
+    }
+    return;
+}
+
+
 =head2 GetDailyQuote($opts)
 
 Takes a hashref of options
@@ -1441,19 +1604,15 @@ sub GetDailyQuote {
         $sth = C4::Context->dbh->prepare('SELECT count(*) FROM quotes;');
         $sth->execute;
         my $range = ($sth->fetchrow_array)[0];
-        if ($range > 1) {
-            # chose a random id within that range if there is more than one quote
-            my $id = int(rand($range));
-            # grab it
-            $query = 'SELECT * FROM quotes WHERE id = ?;';
-            $sth = C4::Context->dbh->prepare($query);
-            $sth->execute($id);
-        }
-        else {
-            $query = 'SELECT * FROM quotes;';
-            $sth = C4::Context->dbh->prepare($query);
-            $sth->execute();
-        }
+        # chose a random id within that range if there is more than one quote
+        my $offset = int(rand($range));
+        # grab it
+        $query = 'SELECT * FROM quotes ORDER BY id LIMIT 1 OFFSET ?';
+        $sth = C4::Context->dbh->prepare($query);
+        # see http://www.perlmonks.org/?node_id=837422 for why
+        # we're being verbose and using bind_param
+        $sth->bind_param(1, $offset, SQL_INTEGER);
+        $sth->execute();
         $quote = $sth->fetchrow_hashref();
         # update the timestamp for that quote
         $query = 'UPDATE quotes SET timestamp = ? WHERE id = ?';
@@ -1475,33 +1634,142 @@ sub _normalize_match_point {
 }
 
 sub _isbn_cleanup {
-    require Business::ISBN;
-    my $isbn = Business::ISBN->new( $_[0] );
-    if ( $isbn ) {
-        $isbn = $isbn->as_isbn10 if $isbn->type eq 'ISBN13';
-        if (defined $isbn) {
-            return $isbn->as_string([]);
+    my ($isbn) = @_;
+    return NormalizeISBN(
+        {
+            isbn          => $isbn,
+            format        => 'ISBN-10',
+            strip_hyphens => 1,
+        }
+    ) if $isbn;
+}
+
+=head2 NormalizedISBN
+
+  my $isbns = NormalizedISBN({
+    isbn => $isbn,
+    strip_hyphens => [0,1],
+    format => ['ISBN-10', 'ISBN-13']
+  });
+
+  Returns an isbn validated by Business::ISBN.
+  Optionally strips hyphens and/or forces the isbn
+  to be of the specified format.
+
+  If the string cannot be validated as an isbn,
+  it returns nothing.
+
+=cut
+
+sub NormalizeISBN {
+    my ($params) = @_;
+
+    my $string        = $params->{isbn};
+    my $strip_hyphens = $params->{strip_hyphens};
+    my $format        = $params->{format};
+
+    return unless $string;
+
+    my $isbn = Business::ISBN->new($string);
+
+    if ( $isbn && $isbn->is_valid() ) {
+
+        if ( $format eq 'ISBN-10' ) {
+            $isbn = $isbn->as_isbn10();
+        }
+        elsif ( $format eq 'ISBN-13' ) {
+            $isbn = $isbn->as_isbn13();
         }
+        return unless $isbn;
+
+        if ($strip_hyphens) {
+            $string = $isbn->as_string( [] );
+        } else {
+            $string = $isbn->as_string();
+        }
+
+        return $string;
     }
-    return;
 }
 
-=head2 Log( $message );
+=head2 GetVariationsOfISBN
 
-  Writes data to /tmp/koha.log.
+  my @isbns = GetVariationsOfISBN( $isbn );
 
-  This is useful for debugging forked processes
-  that do not write to the apache error log
+  Returns a list of varations of the given isbn in
+  both ISBN-10 and ISBN-13 formats, with and without
+  hyphens.
+
+  In a scalar context, the isbns are returned as a
+  string delimited by ' | '.
 
 =cut
 
-sub Log {
-  my ($data) = @_;
-  warn $data;
-  open my $fh, '>>/tmp/koha.log';
-  print $fh "$data\n";
-  close $fh;
+sub GetVariationsOfISBN {
+    my ($isbn) = @_;
+
+    return unless $isbn;
+
+    my @isbns;
+
+    push( @isbns, NormalizeISBN({ isbn => $isbn }) );
+    push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-10' }) );
+    push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-13' }) );
+    push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-10', strip_hyphens => 1 }) );
+    push( @isbns, NormalizeISBN({ isbn => $isbn, format => 'ISBN-13', strip_hyphens => 1 }) );
+
+    # Strip out any "empty" strings from the array
+    @isbns = grep { defined($_) && $_ =~ /\S/ } @isbns;
+
+    return wantarray ? @isbns : join( " | ", @isbns );
 }
+
+=head2 GetVariationsOfISBNs
+
+  my @isbns = GetVariationsOfISBNs( @isbns );
+
+  Returns a list of varations of the given isbns in
+  both ISBN-10 and ISBN-13 formats, with and without
+  hyphens.
+
+  In a scalar context, the isbns are returned as a
+  string delimited by ' | '.
+
+=cut
+
+sub GetVariationsOfISBNs {
+    my (@isbns) = @_;
+
+    @isbns = map { GetVariationsOfISBN( $_ ) } @isbns;
+
+    return wantarray ? @isbns : join( " | ", @isbns );
+}
+
+=head2 IsKohaFieldLinked
+
+    my $is_linked = IsKohaFieldLinked({
+        kohafield => $kohafield,
+        frameworkcode => $frameworkcode,
+    });
+
+    Return 1 if the field is linked
+
+=cut
+
+sub IsKohaFieldLinked {
+    my ( $params ) = @_;
+    my $kohafield = $params->{kohafield};
+    my $frameworkcode = $params->{frameworkcode} || '';
+    my $dbh = C4::Context->dbh;
+    my $is_linked = $dbh->selectcol_arrayref( q|
+        SELECT COUNT(*)
+        FROM marc_subfield_structure
+        WHERE frameworkcode = ?
+        AND kohafield = ?
+    |,{}, $frameworkcode, $kohafield );
+    return $is_linked->[0];
+}
+
 1;
 
 __END__