Bug 32612: (QA follow-up) Add BINMODE method to C4::SIP::Trapper
[koha-ffzg.git] / C4 / Search.pm
index 5842b0e..e6ad8b6 100644 (file)
@@ -21,6 +21,7 @@ use C4::Biblio qw( TransformMarcToKoha GetMarcFromKohaField GetFrameworkCode Get
 use C4::Koha qw( getFacets GetVariationsOfISBN GetNormalizedUPC GetNormalizedEAN GetNormalizedOCLCNumber GetNormalizedISBN getitemtypeimagelocation );
 use Koha::DateUtils;
 use Koha::Libraries;
+use Koha::SearchEngine::QueryBuilder;
 use Lingua::Stem;
 use XML::Simple;
 use C4::XSLT qw( XSLTParse4Display );
@@ -33,6 +34,7 @@ use Koha::Logger;
 use Koha::Patrons;
 use Koha::Recalls;
 use Koha::RecordProcessor;
+use Koha::SearchFilters;
 use URI::Escape;
 use Business::ISBN;
 use MARC::Record;
@@ -85,7 +87,7 @@ This function attempts to find duplicate records using a hard-coded, fairly simp
 sub FindDuplicate {
     my ($record) = @_;
     my $dbh = C4::Context->dbh;
-    my $result = TransformMarcToKoha( $record, '' );
+    my $result = TransformMarcToKoha({ record => $record });
     my $sth;
     my $query;
 
@@ -128,7 +130,7 @@ sub FindDuplicate {
                 $possible_duplicate_record
             );
 
-            my $result = TransformMarcToKoha( $marcrecord, '' );
+            my $result = TransformMarcToKoha({ record => $marcrecord });
 
             # FIXME :: why 2 $biblionumber ?
             if ($result) {
@@ -185,7 +187,7 @@ my @results;
 
 for my $r ( @{$marcresults} ) {
     my $marcrecord = MARC::File::USMARC::decode($r);
-    my $biblio = TransformMarcToKoha($marcrecord,q{});
+    my $biblio = TransformMarcToKoha({ record => $marcrecord });
 
     #build the iarray of hashs for the template.
     push @results, {
@@ -390,6 +392,12 @@ sub getRecords {
             elsif ( $sort eq "title_za" || $sort eq "title_dsc" ) {
                 $sort_by .= "1=4 >i ";
             }
+            elsif ( $sort eq "biblionumber_az" || $sort eq "biblionumber_asc" ) {
+                $sort_by .= "1=12 <i ";
+            }
+            elsif ( $sort eq "biblionumber_za" || $sort eq "biblionumber_dsc" ) {
+                $sort_by .= "1=12 >i ";
+            }
             else {
                 warn "Ignoring unrecognized sort '$sort' requested" if $sort_by;
             }
@@ -1217,7 +1225,7 @@ sub buildQuery {
     my $query_cgi;
     my $query_type;
 
-    my $limit;
+    my $limit = q{};
     my $limit_cgi;
     my $limit_desc;
 
@@ -1231,25 +1239,107 @@ sub buildQuery {
         $query = "ccl=$query" if $cclq;
     }
 
+    # add limits
+    my %group_OR_limits;
+    my $availability_limit;
+    foreach my $this_limit (@limits) {
+        next unless $this_limit;
+        if ( $this_limit =~ /available/ ) {
+#
+## 'available' is defined as (items.onloan is NULL) and (items.itemlost = 0)
+## In English:
+## all records not indexed in the onloan register (zebra) and all records with a value of lost equal to 0
+            $availability_limit .=
+"( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) )";
+            $limit_cgi  .= "&limit=available";
+            $limit_desc .= "";
+        }
+
+        # group_OR_limits, prefixed by mc-
+        # OR every member of the group
+        elsif ( $this_limit =~ /mc/ ) {
+            my ($k,$v) = split(/:/, $this_limit,2);
+            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.'"';
+            }
+
+            $group_OR_limits{$k} .= " or " if $group_OR_limits{$k};
+            $limit_desc      .= " or " if $group_OR_limits{$k};
+            $group_OR_limits{$k} .= "$this_limit";
+            $limit_cgi       .= "&limit=" . uri_escape_utf8($this_limit);
+            $limit_desc      .= " $this_limit";
+        }
+        elsif ( $this_limit =~ '^multibranchlimit:|^branch:' ) {
+            $limit_cgi  .= "&limit=" . uri_escape_utf8($this_limit);
+            $limit .= " and " if $limit || $query;
+            my $branchfield  = C4::Context->preference('SearchLimitLibrary');
+            my @branchcodes;
+            if(  $this_limit =~ '^multibranchlimit:' ){
+                my ($group_id) = ( $this_limit =~ /^multibranchlimit:(.*)$/ );
+                my $search_group = Koha::Library::Groups->find( $group_id );
+                @branchcodes  = map { $_->branchcode } $search_group->all_libraries;
+                @branchcodes = sort { $a cmp $b } @branchcodes;
+            } else {
+                @branchcodes = ( $this_limit =~ /^branch:(.*)$/ );
+            }
+
+            if (@branchcodes) {
+                if ( $branchfield eq "homebranch" ) {
+                    $this_limit = sprintf "(%s)", join " or ", map { 'homebranch: ' . $_ } @branchcodes;
+                }
+                elsif ( $branchfield eq "holdingbranch" ) {
+                    $this_limit = sprintf "(%s)", join " or ", map { 'holdingbranch: ' . $_ } @branchcodes;
+                }
+                else {
+                    $this_limit =  sprintf "(%s or %s)",
+                      join( " or ", map { 'homebranch: ' . $_ } @branchcodes ),
+                      join( " or ", map { 'holdingbranch: ' . $_ } @branchcodes );
+                }
+            }
+            $limit .= "$this_limit";
+            $limit_desc .= " $this_limit";
+        } elsif ( $this_limit =~ '^search_filter:' ) {
+            # Here we will get the query as a string, append to the limits, and pass through buildQuery
+            # again to clean the terms and handle nested filters
+            $limit_cgi  .= "&limit=" . uri_escape_utf8($this_limit);
+            my ($filter_id) = ( $this_limit =~ /^search_filter:(.*)$/ );
+            my $search_filter = Koha::SearchFilters->find( $filter_id );
+            next unless $search_filter;
+            my ($expanded_lim, $query_lim) = $search_filter->expand_filter;
+            push @$expanded_lim, $query_lim;
+            my ( $error, undef, undef, undef, undef, $fixed_limit, undef, undef, undef ) = buildQuery ( undef, undef, undef, $expanded_lim, undef, undef, $lang);
+            $limit .= " and " if $limit || $query;
+            $limit .= "$fixed_limit";
+            $limit_desc .= " $limit";
+        }
+
+        # Regular old limits
+        else {
+            $limit .= " and " if $limit || $query;
+            $limit      .= "$this_limit";
+            $limit_cgi  .= "&limit=" . uri_escape_utf8($this_limit);
+            $limit_desc .= " $this_limit";
+        }
+    }
+    foreach my $k (keys (%group_OR_limits)) {
+        $limit .= " and " if ( $query || $limit );
+        $limit .= "($group_OR_limits{$k})";
+    }
+    if ($availability_limit) {
+        $limit .= " and " if ( $query || $limit );
+        $limit .= "($availability_limit)";
+    }
+
 # for handling ccl, cql, pqf queries in diagnostic mode, skip the rest of the steps
 # DIAGNOSTIC ONLY!!
     if ( $query =~ /^ccl=/ ) {
         my $q=$';
         # This is needed otherwise ccl= and &limit won't work together, and
         # this happens when selecting a subject on the opac-detail page
-        @limits = grep {!/^$/} @limits;
         my $original_q = $q; # without available part
-        unless ( grep { $_ eq 'available' } @limits ) {
-            $q =~ s| and \( \(allrecords,AlwaysMatches=''\) and \(not-onloan-count,st-numeric >= 1\) and \(lost,st-numeric=0\) \)||;
-            $original_q = $q;
-        }
-        if ( @limits ) {
-            if ( grep { $_ eq 'available' } @limits ) {
-                $q .= q| and ( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) )|;
-                @limits = grep {!/^available$/} @limits;
-            }
-            $q .= ' and '.join(' and ', @limits) if @limits;
-        }
+        $q .= $limit if $limit;
         return ( undef, $q, $q, "q=ccl=".uri_escape_utf8($q), $original_q, '', '', '', 'ccl' );
     }
     if ( $query =~ /^cql=/ ) {
@@ -1337,7 +1427,14 @@ sub buildQuery {
                     if ( $index eq 'nb' ) {
                         if ( C4::Context->preference("SearchWithISBNVariations") ) {
                             my @isbns = C4::Koha::GetVariationsOfISBN( $operand );
-                            $operands[$i] = $operand =  '(nb=' . join(' OR nb=', @isbns) . ')';
+                            $operands[$i] = $operand = '(' . join( ' OR ', map { 'nb=' . $_ } @isbns ) . ')';
+                            $indexes[$i] = $index = 'kw';
+                        }
+                    }
+                    if ( $index eq 'ns' ) {
+                        if ( C4::Context->preference("SearchWithISSNVariations") ) {
+                            my @issns = C4::Koha::GetVariationsOfISSN( $operand );
+                            $operands[$i] = $operand = '(' . join( ' OR ', map { 'ns=' . $_ } @issns ) . ')';
                             $indexes[$i] = $index = 'kw';
                         }
                     }
@@ -1446,86 +1543,6 @@ sub buildQuery {
     }
     Koha::Logger->get->debug("QUERY BEFORE LIMITS: >$query<");
 
-    # add limits
-    my %group_OR_limits;
-    my $availability_limit;
-    foreach my $this_limit (@limits) {
-        next unless $this_limit;
-        if ( $this_limit =~ /available/ ) {
-#
-## 'available' is defined as (items.onloan is NULL) and (items.itemlost = 0)
-## In English:
-## all records not indexed in the onloan register (zebra) and all records with a value of lost equal to 0
-            $availability_limit .=
-"( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) )";
-            $limit_cgi  .= "&limit=available";
-            $limit_desc .= "";
-        }
-
-        # group_OR_limits, prefixed by mc-
-        # OR every member of the group
-        elsif ( $this_limit =~ /mc/ ) {
-            my ($k,$v) = split(/:/, $this_limit,2);
-            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.'"';
-            }
-
-            $group_OR_limits{$k} .= " or " if $group_OR_limits{$k};
-            $limit_desc      .= " or " if $group_OR_limits{$k};
-            $group_OR_limits{$k} .= "$this_limit";
-            $limit_cgi       .= "&limit=" . uri_escape_utf8($this_limit);
-            $limit_desc      .= " $this_limit";
-        }
-        elsif ( $this_limit =~ '^multibranchlimit:|^branch:' ) {
-            $limit_cgi  .= "&limit=" . uri_escape_utf8($this_limit);
-            $limit .= " and " if $limit || $query;
-            my $branchfield  = C4::Context->preference('SearchLimitLibrary');
-            my @branchcodes;
-            if(  $this_limit =~ '^multibranchlimit:' ){
-                my ($group_id) = ( $this_limit =~ /^multibranchlimit:(.*)$/ );
-                my $search_group = Koha::Library::Groups->find( $group_id );
-                @branchcodes  = map { $_->branchcode } $search_group->all_libraries;
-                @branchcodes = sort { $a cmp $b } @branchcodes;
-            } else {
-                @branchcodes = ( $this_limit =~ /^branch:(.*)$/ );
-            }
-
-            if (@branchcodes) {
-                if ( $branchfield eq "homebranch" ) {
-                    $this_limit = sprintf "(%s)", join " or ", map { 'homebranch: ' . $_ } @branchcodes;
-                }
-                elsif ( $branchfield eq "holdingbranch" ) {
-                    $this_limit = sprintf "(%s)", join " or ", map { 'holdingbranch: ' . $_ } @branchcodes;
-                }
-                else {
-                    $this_limit =  sprintf "(%s or %s)",
-                      join( " or ", map { 'homebranch: ' . $_ } @branchcodes ),
-                      join( " or ", map { 'holdingbranch: ' . $_ } @branchcodes );
-                }
-            }
-            $limit .= "$this_limit";
-            $limit_desc .= " $this_limit";
-        }
-
-        # Regular old limits
-        else {
-            $limit .= " and " if $limit || $query;
-            $limit      .= "$this_limit";
-            $limit_cgi  .= "&limit=" . uri_escape_utf8($this_limit);
-            $limit_desc .= " $this_limit";
-        }
-    }
-    foreach my $k (keys (%group_OR_limits)) {
-        $limit .= " and " if ( $query || $limit );
-        $limit .= "($group_OR_limits{$k})";
-    }
-    if ($availability_limit) {
-        $limit .= " and " if ( $query || $limit );
-        $limit .= "($availability_limit)";
-    }
-
     # Normalize the query and limit strings
     # This is flawed , means we can't search anything with : in it
     # if user wants to do ccl or cql, start the query with that
@@ -1718,7 +1735,7 @@ sub searchResults {
                : GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf));
 
         SetUTF8Flag($marcrecord);
-        my $oldbiblio = TransformMarcToKoha( $marcrecord, $fw, 'no_items' );
+        my $oldbiblio = TransformMarcToKoha({ record => $marcrecord, limit_table => 'no_items' });
         $oldbiblio->{result_number} = $i + 1;
 
                $oldbiblio->{normalized_upc}  = GetNormalizedUPC(       $marcrecord,$marcflavour);
@@ -1800,23 +1817,24 @@ sub searchResults {
             foreach my $code ( keys %subfieldstosearch ) {
                 $item->{$code} = $field->subfield( $subfieldstosearch{$code} );
             }
+
+            unless ( $item->{itemnumber} ) {
+                warn "MARC item without itemnumber retrieved for biblio ($oldbiblio->{biblionumber})";
+                next;
+            }
+
             $item->{description} = $itemtypes{ $item->{itype} }{translated_description} if $item->{itype};
 
-               # OPAC hidden items
+            # OPAC hidden items
             if ($is_opac) {
-                # hidden because lost
-                if ($hidelostitems && $item->{itemlost}) {
+                # hidden based on OpacHiddenItems syspref or because lost
+                my $hi = Koha::Items->search( { itemnumber => $item->{itemnumber} } )
+                                    ->filter_by_visible_in_opac({ patron => $search_context->{patron} });
+                unless ( $hi->count ) {
                     push @hiddenitems, $item->{itemnumber};
                     $hideatopac_count++;
                     next;
                 }
-                # hidden based on OpacHiddenItems syspref
-                my @hi = C4::Items::GetHiddenItemnumbers({ items=> [ $item ], borcat => $search_context->{category} });
-                if (scalar @hi) {
-                    push @hiddenitems, @hi;
-                    $hideatopac_count++;
-                    next;
-                }
             }
 
             my $hbranch     = C4::Context->preference('StaffSearchResultsDisplayBranch');
@@ -1900,8 +1918,16 @@ sub searchResults {
                     # FIXME: to avoid having the query the database like this, and to make
                     #        the in transit status count as unavailable for search limiting,
                     #        should map transit status to record indexed in Zebra.
-                    #
-                    ($transfertwhen, $transfertfrom, $transfertto) = C4::Circulation::GetTransfers($item->{itemnumber});
+
+                    my $item_object = Koha::Items->find($item->{itemnumber});
+                    my $transfer = defined($item_object) ? $item_object->get_transfer : undef;
+                    ( $transfertwhen, $transfertfrom, $transfertto ) =
+                      defined($transfer)
+                      ? (
+                        $transfer->datesent, $transfer->frombranch,
+                        $transfer->tobranch
+                      )
+                      : ( '', '', '' );
                     $reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber} );
                     if ( C4::Context->preference('UseRecalls') ) {
                         if ( Koha::Recalls->search({ item_id => $item->{itemnumber}, status => 'waiting' })->count ) {
@@ -1995,13 +2021,17 @@ sub searchResults {
                     ),
                     fix_amps       => 1,
                     hidden_items   => \@hiddenitems,
-                    xslt_variables => $xslt_variables
+                    xslt_variables => $xslt_variables,
                 }
             );
         }
 
         my $biblio_object = Koha::Biblios->find( $oldbiblio->{biblionumber} );
         $oldbiblio->{biblio_object} = $biblio_object;
+        $oldbiblio->{coins} = eval { $biblio_object->get_coins }
+          if $biblio_object
+          && C4::Context->preference('COinSinOPACResults')
+          && $is_opac;
 
         my $can_place_holds = 1;
         # if biblio level itypes are used and itemtype is notforloan, it can't be reserved either