Bug 17600: Standardize our EXPORT_OK
[srvgit] / C4 / Search.pm
index 8997dba..1ca987a 100644 (file)
@@ -16,33 +16,43 @@ package C4::Search;
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-require Exporter;
 use C4::Context;
-use C4::Biblio;    # GetMarcFromKohaField, GetBiblioData
-use C4::Koha;      # getFacets
+use C4::Biblio qw( TransformMarcToKoha GetMarcFromKohaField GetFrameworkCode GetAuthorisedValueDesc GetBiblioData );
+use C4::Koha qw( getFacets GetVariationsOfISBN GetNormalizedUPC GetNormalizedEAN GetNormalizedOCLCNumber GetNormalizedISBN getitemtypeimagelocation );
 use Koha::DateUtils;
 use Koha::Libraries;
 use Lingua::Stem;
-use C4::Search::PazPar2;
 use XML::Simple;
-use C4::XSLT;
-use C4::Reserves;    # GetReserveStatus
-use C4::Debug;
-use C4::Charset;
+use C4::XSLT qw( XSLTParse4Display );
+use C4::Reserves qw( GetReserveStatus );
+use C4::Charset qw( SetUTF8Flag );
 use Koha::AuthorisedValues;
 use Koha::ItemTypes;
 use Koha::Libraries;
+use Koha::Logger;
 use Koha::Patrons;
 use Koha::RecordProcessor;
-use YAML;
 use URI::Escape;
 use Business::ISBN;
 use MARC::Record;
 use MARC::Field;
-use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG);
 
+our (@ISA, @EXPORT_OK);
 BEGIN {
-    $DEBUG = ($ENV{DEBUG}) ? 1 : 0;
+    require Exporter;
+    @ISA    = qw(Exporter);
+    @EXPORT_OK = qw(
+      FindDuplicate
+      SimpleSearch
+      searchResults
+      getRecords
+      buildQuery
+      GetDistinctValues
+      enabled_staff_search_views
+      new_record_from_zebra
+      z3950_search_args
+      getIndexes
+    );
 }
 
 =head1 NAME
@@ -61,17 +71,6 @@ This module provides searching functions for Koha's bibliographic databases
 
 =cut
 
-@ISA    = qw(Exporter);
-@EXPORT = qw(
-  &FindDuplicate
-  &SimpleSearch
-  &searchResults
-  &getRecords
-  &buildQuery
-  &GetDistinctValues
-  &enabled_staff_search_views
-);
-
 # make all your functions, whether exported or not;
 
 =head2 FindDuplicate
@@ -88,9 +87,6 @@ sub FindDuplicate {
     my $result = TransformMarcToKoha( $record, '' );
     my $sth;
     my $query;
-    my $search;
-    my $type;
-    my ( $biblionumber, $title );
 
     # search duplicate on ISBN, easy and fast..
     # ... normalize first
@@ -110,9 +106,6 @@ sub FindDuplicate {
         $result->{title} =~ s /\(//g;
         $result->{title} =~ s /\)//g;
 
-        # FIXME: instead of removing operators, could just do
-        # quotes around the value
-        $result->{title} =~ s/(and|or|not)//g;
         $query = "$titleindex:\"$result->{title}\"";
         if   ( $result->{author} ) {
             $result->{author} =~ s /\\//g;
@@ -120,13 +113,12 @@ sub FindDuplicate {
             $result->{author} =~ s /\(//g;
             $result->{author} =~ s /\)//g;
 
-            # remove valid operators
-            $result->{author} =~ s/(and|or|not)//g;
             $query .= " $op $authorindex:\"$result->{author}\"";
         }
     }
 
-    my ( $error, $searchresults, undef ) = SimpleSearch($query); # FIXME :: hardcoded !
+    my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
+    my ( $error, $searchresults, undef ) = $searcher->simple_search_compat($query,0,50);
     my @results;
     if (!defined $error) {
         foreach my $possible_duplicate_record (@{$searchresults}) {
@@ -227,7 +219,7 @@ sub SimpleSearch {
         eval {
             $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
             $query =~ s/:/=/g unless $options{skip_normalize};
-            $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]);
+            $zoom_queries[$i] = ZOOM::Query::CCL2RPN->new( $query, $zconns[$i]);
             $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] );
 
             # error handling
@@ -310,7 +302,6 @@ sub getRecords {
     $offset = 0 if $offset < 0;
 
     # Initialize variables for the ZOOM connection and results object
-    my $zconn;
     my @zconns;
     my @results;
     my $results_hashref = ();
@@ -333,26 +324,25 @@ sub getRecords {
 # if this is a local search, use the $koha-query, if it's a federated one, use the federated-query
         my $query_to_use = ($servers[$i] =~ /biblioserver/) ? $koha_query : $simple_query;
 
-        #$query_to_use = $simple_query if $scan;
-        warn $simple_query if ( $scan and $DEBUG );
+        Koha::Logger->get->debug($simple_query) if $scan;
 
         # Check if we've got a query_type defined, if so, use it
         eval {
             if ($query_type) {
                 if ($query_type =~ /^ccl/) {
                     $query_to_use =~ s/\:/\=/g;    # change : to = last minute (FIXME)
-                    $results[$i] = $zconns[$i]->search(new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i]));
+                    $results[$i] = $zconns[$i]->search(ZOOM::Query::CCL2RPN->new($query_to_use, $zconns[$i]));
                 } elsif ($query_type =~ /^cql/) {
-                    $results[$i] = $zconns[$i]->search(new ZOOM::Query::CQL($query_to_use, $zconns[$i]));
+                    $results[$i] = $zconns[$i]->search(ZOOM::Query::CQL->new($query_to_use, $zconns[$i]));
                 } elsif ($query_type =~ /^pqf/) {
-                    $results[$i] = $zconns[$i]->search(new ZOOM::Query::PQF($query_to_use, $zconns[$i]));
+                    $results[$i] = $zconns[$i]->search(ZOOM::Query::PQF->new($query_to_use, $zconns[$i]));
                 } else {
                     warn "Unknown query_type '$query_type'.  Results undetermined.";
                 }
             } elsif ($scan) {
-                    $results[$i] = $zconns[$i]->scan(  new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i]));
+                    $results[$i] = $zconns[$i]->scan(  ZOOM::Query::CCL2RPN->new($query_to_use, $zconns[$i]));
             } else {
-                    $results[$i] = $zconns[$i]->search(new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i]));
+                    $results[$i] = $zconns[$i]->search(ZOOM::Query::CCL2RPN->new($query_to_use, $zconns[$i]));
             }
         };
         if ($@) {
@@ -429,7 +419,6 @@ sub getRecords {
                 }
 
                 for ( my $j = $offset ; $j < $times ; $j++ ) {
-                    my $records_hash;
                     my $record;
 
                     ## Check if it's an index scan
@@ -685,6 +674,7 @@ sub _get_facets_data_from_record {
                 next if $field->indicator(1) eq 'z';
 
                 my $data = $field->as_string( $subfield_letters, $facet->{ sep } );
+                $data =~ s/\s*(?<!\p{Uppercase})[.\-,;]*\s*$//;
 
                 unless ( grep { $_ eq $data } @used_datas ) {
                     push @used_datas, $data;
@@ -783,8 +773,9 @@ sub _get_facet_from_result_set {
     my $facets = {};
     foreach my $term ( @terms ) {
         my $facet_value = $term->textContent;
+        $facet_value =~ s/\s*(?<!\p{Uppercase})[.\-,;]*\s*$//;
         $facet_value =~ s/\Q$internal_sep\E/$sep/ if defined $sep;
-        $facets->{ $facet_value } = $term->getAttribute( 'occur' );
+        $facets->{ $facet_value } += $term->getAttribute( 'occur' );
     }
 
     return $facets;
@@ -812,80 +803,6 @@ sub _get_facets_info {
     return $facets_info;
 }
 
-sub pazGetRecords {
-    my (
-        $koha_query,       $simple_query, $sort_by_ref,    $servers_ref,
-        $results_per_page, $offset,       $branches,       $query_type,
-        $scan
-    ) = @_;
-    #NOTE: Parameter $branches is not used here !
-
-    my $paz = C4::Search::PazPar2->new(C4::Context->config('pazpar2url'));
-    $paz->init();
-    $paz->search($simple_query);
-    sleep 1;   # FIXME: WHY?
-
-    # do results
-    my $results_hashref = {};
-    my $stats = XMLin($paz->stat);
-    my $results = XMLin($paz->show($offset, $results_per_page, 'work-title:1'), forcearray => 1);
-
-    # for a grouped search result, the number of hits
-    # is the number of groups returned; 'bib_hits' will have
-    # the total number of bibs.
-    $results_hashref->{'biblioserver'}->{'hits'} = $results->{'merged'}->[0];
-    $results_hashref->{'biblioserver'}->{'bib_hits'} = $stats->{'hits'};
-
-    HIT: foreach my $hit (@{ $results->{'hit'} }) {
-        my $recid = $hit->{recid}->[0];
-
-        my $work_title = $hit->{'md-work-title'}->[0];
-        my $work_author;
-        if (exists $hit->{'md-work-author'}) {
-            $work_author = $hit->{'md-work-author'}->[0];
-        }
-        my $group_label = (defined $work_author) ? "$work_title / $work_author" : $work_title;
-
-        my $result_group = {};
-        $result_group->{'group_label'} = $group_label;
-        $result_group->{'group_merge_key'} = $recid;
-
-        my $count = 1;
-        if (exists $hit->{count}) {
-            $count = $hit->{count}->[0];
-        }
-        $result_group->{'group_count'} = $count;
-
-        for (my $i = 0; $i < $count; $i++) {
-            # FIXME -- may need to worry about diacritics here
-            my $rec = $paz->record($recid, $i);
-            push @{ $result_group->{'RECORDS'} }, $rec;
-        }
-
-        push @{ $results_hashref->{'biblioserver'}->{'GROUPS'} }, $result_group;
-    }
-
-    # pass through facets
-    my $termlist_xml = $paz->termlist('author,subject');
-    my $terms = XMLin($termlist_xml, forcearray => 1);
-    my @facets_loop = ();
-    #die Dumper($results);
-#    foreach my $list (sort keys %{ $terms->{'list'} }) {
-#        my @facets = ();
-#        foreach my $facet (sort @{ $terms->{'list'}->{$list}->{'term'} } ) {
-#            push @facets, {
-#                facet_label_value => $facet->{'name'}->[0],
-#            };
-#        }
-#        push @facets_loop, ( {
-#            type_label => $list,
-#            facets => \@facets,
-#        } );
-#    }
-
-    return ( undef, $results_hashref, \@facets_loop );
-}
-
 # TRUNCATION
 sub _detect_truncation {
     my ( $operand, $index ) = @_;
@@ -952,7 +869,8 @@ sub _build_stemmed_operand {
           unless ( $stem =~ /(and$|or$|not$)/ ) || ( length($stem) < 3 );
         $stemmed_operand .= " ";
     }
-    warn "STEMMED OPERAND: $stemmed_operand" if $DEBUG;
+
+    Koha::Logger->get->debug("STEMMED OPERAND: $stemmed_operand");
     return $stemmed_operand;
 }
 
@@ -1270,8 +1188,6 @@ See verbose embedded documentation.
 sub buildQuery {
     my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
 
-    warn "---------\nEnter buildQuery\n---------" if $DEBUG;
-
     my $query_desc;
 
     # dereference
@@ -1286,7 +1202,7 @@ sub buildQuery {
     my $weight_fields    = C4::Context->preference("QueryWeightFields")    || 0;
     my $fuzzy_enabled    = C4::Context->preference("QueryFuzzy")           || 0;
 
-    my $query        = $operands[0];
+    my $query        = $operands[0] // "";
     my $simple_query = $operands[0];
 
     # initialize the variables we're passing back
@@ -1381,7 +1297,7 @@ sub buildQuery {
 
                 # Add index-specific attributes
 
-                #Afaik, this 'yr' condition will only ever be met in the staff client advanced search
+                #Afaik, this 'yr' condition will only ever be met in the staff interface advanced search
                 #for "Publication date", since typing 'yr:YYYY' into the search box produces a CCL query,
                 #which is processed higher up in this sub. Other than that, year searches are typically
                 #handled as limits which are not processed her either.
@@ -1435,18 +1351,17 @@ sub buildQuery {
                                                $operand=join(" ",map{
                                                                                        (index($_,"*")>0?"$_":"$_*")
                                                                                         }split (/\s+/,$operand));
-                                               warn $operand if $DEBUG;
                                        }
                                }
 
                 # Detect Truncation
-                my $truncated_operand;
+                my $truncated_operand = q{};
                 my( $nontruncated, $righttruncated, $lefttruncated,
                     $rightlefttruncated, $regexpr
                 ) = _detect_truncation( $operand, $index );
-                warn
-"TRUNCATION: NON:>@$nontruncated< RIGHT:>@$righttruncated< LEFT:>@$lefttruncated< RIGHTLEFT:>@$rightlefttruncated< REGEX:>@$regexpr<"
-                  if $DEBUG;
+
+                Koha::Logger->get->debug(
+                    "TRUNCATION: NON:>@$nontruncated< RIGHT:>@$righttruncated< LEFT:>@$lefttruncated< RIGHTLEFT:>@$rightlefttruncated< REGEX:>@$regexpr<");
 
                 # Apply Truncation
                 if (
@@ -1479,24 +1394,31 @@ sub buildQuery {
                     }
                 }
                 $operand = $truncated_operand if $truncated_operand;
-                warn "TRUNCATED OPERAND: >$truncated_operand<" if $DEBUG;
+                Koha::Logger->get->debug("TRUNCATED OPERAND: >$truncated_operand<");
 
                 # Handle Stemming
-                my $stemmed_operand;
+                my $stemmed_operand = q{};
                 $stemmed_operand = _build_stemmed_operand($operand, $lang)
                                                                                if $stemming;
 
-                warn "STEMMED OPERAND: >$stemmed_operand<" if $DEBUG;
+                Koha::Logger->get->debug("STEMMED OPERAND: >$stemmed_operand<");
 
                 # Handle Field Weighting
-                my $weighted_operand;
+                my $weighted_operand = q{};
                 if ($weight_fields) {
                     $weighted_operand = _build_weighted_query( $operand, $stemmed_operand, $index );
                     $operand = $weighted_operand;
                     $indexes_set = 1;
                 }
 
-                warn "FIELD WEIGHTED OPERAND: >$weighted_operand<" if $DEBUG;
+                Koha::Logger->get->debug("FIELD WEIGHTED OPERAND: >$weighted_operand<");
+
+                #Use relevance ranking when not using a weighted query (which adds relevance ranking of its own)
+
+                #N.B. Truncation is mutually exclusive with Weighted Queries,
+                #so even if QueryWeightFields is turned on, QueryAutoTruncate will turn it off, thus
+                #the need for this relevance wrapper.
+                $operand = "(rk=($operand))" unless $weight_fields;
 
                 ($query,$query_cgi,$query_desc,$previous_operand) = _build_initial_query({
                     query => $query,
@@ -1514,7 +1436,7 @@ sub buildQuery {
             }    #/if $operands
         }    # /for
     }
-    warn "QUERY BEFORE LIMITS: >$query<" if $DEBUG;
+    Koha::Logger->get->debug("QUERY BEFORE LIMITS: >$query<");
 
     # add limits
     my %group_OR_limits;
@@ -1539,7 +1461,7 @@ sub buildQuery {
             if ( $k !~ /mc-i(tem)?type/ ) {
                 # in case the mc-ccode value has complicating chars like ()'s inside it we wrap in quotes
                 $this_limit =~ tr/"//d;
-                $this_limit = $k.":'".$v."'";
+                $this_limit = $k.':"'.$v.'"';
             }
 
             $group_OR_limits{$k} .= " or " if $group_OR_limits{$k};
@@ -1589,11 +1511,13 @@ sub buildQuery {
     $query =~ s/(?<=(st-date-normalized)):/=/g;
 
     # Removing warnings for later substitutions
-    $query      //= q{};
-    $query_desc //= q{};
-    $query_cgi  //= q{};
-    $limit      //= q{};
-    $limit_desc //= q{};
+    $query        //= q{};
+    $query_desc   //= q{};
+    $query_cgi    //= q{};
+    $limit        //= q{};
+    $limit_desc   //= q{};
+    $limit_cgi    //= q{};
+    $simple_query //= q{};
     $limit =~ s/:/=/g;
     for ( $query, $query_desc, $limit, $limit_desc ) {
         s/  +/ /g;    # remove extra spaces
@@ -1609,16 +1533,9 @@ sub buildQuery {
     # append the limit to the query
     $query .= " " . $limit;
 
-    # Warnings if DEBUG
-    if ($DEBUG) {
-        warn "QUERY:" . $query;
-        warn "QUERY CGI:" . $query_cgi;
-        warn "QUERY DESC:" . $query_desc;
-        warn "LIMIT:" . $limit;
-        warn "LIMIT CGI:" . $limit_cgi;
-        warn "LIMIT DESC:" . $limit_desc;
-        warn "---------\nLeave buildQuery\n---------";
-    }
+    Koha::Logger->get->debug(
+        sprintf "buildQuery returns\nQUERY:%s\nQUERY CGI:%s\nQUERY DESC:%s\nLIMIT:%s\nLIMIT CGI:%s\nLIMIT DESC:%s",
+        $query, $query_cgi, $query_desc, $limit, $limit_cgi, $limit_desc );
 
     return (
         undef,              $query, $simple_query, $query_cgi,
@@ -1725,12 +1642,12 @@ sub searchResults {
     }
 
     # handle which records to actually retrieve
-    my $times;
+    my $times; # Times is which record to process up to
     if ( $hits && $offset + $results_per_page <= $hits ) {
         $times = $offset + $results_per_page;
     }
     else {
-        $times = $hits;         # FIXME: if $hits is undefined, why do we want to equal it?
+        $times = $hits; # If less hits than results_per_page+offset we go to the end
     }
 
     my $marcflavour = C4::Context->preference("marcflavour");
@@ -1784,13 +1701,13 @@ sub searchResults {
                : GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf));
 
         SetUTF8Flag($marcrecord);
-        my $oldbiblio = TransformMarcToKoha( $marcrecord, $fw );
+        my $oldbiblio = TransformMarcToKoha( $marcrecord, $fw, 'no_items' );
         $oldbiblio->{result_number} = $i + 1;
 
                $oldbiblio->{normalized_upc}  = GetNormalizedUPC(       $marcrecord,$marcflavour);
                $oldbiblio->{normalized_ean}  = GetNormalizedEAN(       $marcrecord,$marcflavour);
                $oldbiblio->{normalized_oclc} = GetNormalizedOCLCNumber($marcrecord,$marcflavour);
-               $oldbiblio->{normalized_isbn} = GetNormalizedISBN(undef,$marcrecord,$marcflavour);
+        $oldbiblio->{normalized_isbn} = GetNormalizedISBN($oldbiblio->{isbn},$marcrecord,$marcflavour); # Use existing ISBN from record if we got one
                $oldbiblio->{content_identifier_exists} = 1 if ($oldbiblio->{normalized_isbn} or $oldbiblio->{normalized_oclc} or $oldbiblio->{normalized_ean} or $oldbiblio->{normalized_upc});
 
                # edition information, if any
@@ -1953,7 +1870,7 @@ sub searchResults {
                 $onloan_items->{$key}->{due_date} = $item->{onloan};
                 $onloan_items->{$key}->{count}++ if $item->{$hbranch};
                 $onloan_items->{$key}->{branchname}     = $item->{branchname};
-                $onloan_items->{$key}->{location}       = $shelflocations->{ $item->{location} };
+                $onloan_items->{$key}->{location}       = $shelflocations->{ $item->{location} } if $item->{location};
                 $onloan_items->{$key}->{itemcallnumber} = $item->{itemcallnumber};
                 $onloan_items->{$key}->{description}    = $item->{description};
                 $onloan_items->{$key}->{imageurl} =
@@ -2069,7 +1986,7 @@ sub searchResults {
         }    # notforloan, item level and biblioitem level
 
         # if all items are hidden, do not show the record
-        if ($items_count > 0 && $hideatopac_count == $items_count) {
+        if ( C4::Context->preference('OpacHiddenItemsHidesRecord') && $items_count > 0 && $hideatopac_count == $items_count) {
             next;
         }
 
@@ -2275,7 +2192,6 @@ sub GetDistinctValues {
     if ($fieldname=~/\./){
                        my ($table,$column)=split /\./, $fieldname;
                        my $dbh = C4::Context->dbh;
-                       warn "select DISTINCT($column) as value, count(*) as cnt from $table group by lib order by $column " if $DEBUG;
                        my $sth = $dbh->prepare("select DISTINCT($column) as value, count(*) as cnt from $table ".($string?" where $column like \"$string%\"":"")."group by value order by $column ");
                        $sth->execute;
                        my $elements=$sth->fetchall_arrayref({});