Bug 9239 QA follow-up: fix highlighting and merge error
[koha.git] / C4 / Search.pm
index 7c20780..8147e52 100644 (file)
@@ -28,7 +28,7 @@ use C4::Dates qw(format_date);
 use C4::Members qw(GetHideLostItemsPreference);
 use C4::XSLT;
 use C4::Branch;
-use C4::Reserves;    # CheckReserves
+use C4::Reserves;    # GetReserveStatus
 use C4::Debug;
 use C4::Charset;
 use YAML;
@@ -100,9 +100,25 @@ sub FindDuplicate {
     if ( $result->{isbn} ) {
         $result->{isbn} =~ s/\(.*$//;
         $result->{isbn} =~ s/\s+$//;
-        $query = "isbn=$result->{isbn}";
+        $query = "isbn:$result->{isbn}";
     }
     else {
+        my $QParser;
+        $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
+        my $titleindex;
+        my $authorindex;
+        my $op;
+
+        if ($QParser) {
+            $titleindex = 'title|exact';
+            $authorindex = 'author|exact';
+            $op = '&&';
+        } else {
+            $titleindex = 'ti,ext';
+            $authorindex = 'au,ext';
+            $op = 'and';
+        }
+
         $result->{title} =~ s /\\//g;
         $result->{title} =~ s /\"//g;
         $result->{title} =~ s /\(//g;
@@ -111,9 +127,7 @@ sub FindDuplicate {
         # FIXME: instead of removing operators, could just do
         # quotes around the value
         $result->{title} =~ s/(and|or|not)//g;
-        $query = "ti,ext=$result->{title}";
-        $query .= " and itemtype=$result->{itemtype}"
-          if ( $result->{itemtype} );
+        $query = "$titleindex:\"$result->{title}\"";
         if   ( $result->{author} ) {
             $result->{author} =~ s /\\//g;
             $result->{author} =~ s /\"//g;
@@ -122,7 +136,7 @@ sub FindDuplicate {
 
             # remove valid operators
             $result->{author} =~ s/(and|or|not)//g;
-            $query .= " and au,ext=$result->{author}";
+            $query .= " $op $authorindex:\"$result->{author}\"";
         }
     }
 
@@ -226,11 +240,21 @@ sub SimpleSearch {
         my $results = [];
         my $total_hits = 0;
 
+        my $QParser;
+        $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') && ! ($query =~ m/\w,\w|\w=\w/));
+
         # Initialize & Search Zebra
         for ( my $i = 0 ; $i < @servers ; $i++ ) {
             eval {
                 $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
-                $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]);
+                if ($QParser) {
+                    $query =~ s/=/:/g;
+                    $QParser->parse( $query );
+                    $query = $QParser->target_syntax($servers[$i]);
+                    $zoom_queries[$i] = new ZOOM::Query::PQF( $query, $zconns[$i]);
+                } else {
+                    $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]);
+                }
                 $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] );
 
                 # error handling
@@ -254,28 +278,29 @@ sub SimpleSearch {
                 return ( $error, undef, undef );
             }
         }
-        while ( ( my $i = ZOOM::event( \@zconns ) ) != 0 ) {
-            my $event = $zconns[ $i - 1 ]->last_event();
-            if ( $event == ZOOM::Event::ZEND ) {
 
-                my $first_record = defined( $offset ) ? $offset+1 : 1;
+        _ZOOM_event_loop(
+            \@zconns,
+            \@tmpresults,
+            sub {
+                my ($i, $size) = @_;
+                my $first_record = defined($offset) ? $offset + 1 : 1;
                 my $hits = $tmpresults[ $i - 1 ]->size();
                 $total_hits += $hits;
                 my $last_record = $hits;
                 if ( defined $max_results && $offset + $max_results < $hits ) {
-                    $last_record  = $offset + $max_results;
+                    $last_record = $offset + $max_results;
                 }
 
-                for my $j ( $first_record..$last_record ) {
-                    my $record = $tmpresults[ $i - 1 ]->record( $j-1 )->raw(); # 0 indexed
+                for my $j ( $first_record .. $last_record ) {
+                    my $record =
+                      $tmpresults[ $i - 1 ]->record( $j - 1 )->raw()
+                      ;    # 0 indexed
                     push @{$results}, $record;
                 }
             }
-        }
+        );
 
-        foreach my $result (@tmpresults) {
-            $result->destroy();
-        }
         foreach my $zoom_query (@zoom_queries) {
             $zoom_query->destroy();
         }
@@ -410,12 +435,11 @@ sub getRecords {
     }    # finished looping through servers
 
     # The big moment: asynchronously retrieve results from all servers
-    while ( ( my $i = ZOOM::event( \@zconns ) ) != 0 ) {
-        my $ev = $zconns[ $i - 1 ]->last_event();
-        if ( $ev == ZOOM::Event::ZEND ) {
-            next unless $results[ $i - 1 ];
-            my $size = $results[ $i - 1 ]->size();
-            if ( $size > 0 ) {
+        _ZOOM_event_loop(
+            \@zconns,
+            \@results,
+            sub {
+                my ( $i, $size ) = @_;
                 my $results_hash;
 
                 # loop through the results
@@ -444,16 +468,26 @@ sub getRecords {
                         my $tmpauthor;
 
                 # the minimal record in author/title (depending on MARC flavour)
-                        if (C4::Context->preference("marcflavour") eq "UNIMARC") {
-                            $tmptitle = MARC::Field->new('200',' ',' ', a => $term, f => $occ);
+                        if ( C4::Context->preference("marcflavour") eq
+                            "UNIMARC" )
+                        {
+                            $tmptitle = MARC::Field->new(
+                                '200', ' ', ' ',
+                                a => $term,
+                                f => $occ
+                            );
                             $tmprecord->append_fields($tmptitle);
-                        } else {
-                            $tmptitle  = MARC::Field->new('245',' ',' ', a => $term,);
-                            $tmpauthor = MARC::Field->new('100',' ',' ', a => $occ,);
+                        }
+                        else {
+                            $tmptitle =
+                              MARC::Field->new( '245', ' ', ' ', a => $term, );
+                            $tmpauthor =
+                              MARC::Field->new( '100', ' ', ' ', a => $occ, );
                             $tmprecord->append_fields($tmptitle);
                             $tmprecord->append_fields($tmpauthor);
                         }
-                        $results_hash->{'RECORDS'}[$j] = $tmprecord->as_usmarc();
+                        $results_hash->{'RECORDS'}[$j] =
+                          $tmprecord->as_usmarc();
                     }
 
                     # not an index scan
@@ -467,143 +501,179 @@ sub getRecords {
                 }
                 $results_hashref->{ $servers[ $i - 1 ] } = $results_hash;
 
-                # Fill the facets while we're looping, but only for the biblioserver and not for a scan
+# Fill the facets while we're looping, but only for the biblioserver and not for a scan
                 if ( !$scan && $servers[ $i - 1 ] =~ /biblioserver/ ) {
 
-                    my $jmax = $size>$facets_maxrecs? $facets_maxrecs: $size;
-                    for my $facet ( @$facets ) {
-                               for ( my $j = 0 ; $j < $jmax ; $j++ ) {
-                                   my $render_record = $results[ $i - 1 ]->record($j)->render();
+                    my $jmax =
+                      $size > $facets_maxrecs ? $facets_maxrecs : $size;
+                    for my $facet (@$facets) {
+                        for ( my $j = 0 ; $j < $jmax ; $j++ ) {
+                            my $render_record =
+                              $results[ $i - 1 ]->record($j)->render();
                             my @used_datas = ();
-                            foreach my $tag ( @{$facet->{tags}} ) {
+                            foreach my $tag ( @{ $facet->{tags} } ) {
+
                                 # avoid first line
-                                my $tag_num = substr($tag, 0, 3);
-                                my $letters = substr($tag, 3);
-                                my $field_pattern = '\n' . $tag_num . ' ([^z][^\n]+)';
-                                $field_pattern = '\n' . $tag_num . ' ([^\n]+)' if (int($tag_num) < 10);
-                                my @field_tokens = ( $render_record =~ /$field_pattern/g ) ;
+                                my $tag_num = substr( $tag, 0, 3 );
+                                my $letters = substr( $tag, 3 );
+                                my $field_pattern =
+                                  '\n' . $tag_num . ' ([^z][^\n]+)';
+                                $field_pattern = '\n' . $tag_num . ' ([^\n]+)'
+                                  if ( int($tag_num) < 10 );
+                                my @field_tokens =
+                                  ( $render_record =~ /$field_pattern/g );
                                 foreach my $field_token (@field_tokens) {
-                                    my @subf = ( $field_token =~ /\$([a-zA-Z0-9]) ([^\$]+)/g );
+                                    my @subf = ( $field_token =~
+                                          /\$([a-zA-Z0-9]) ([^\$]+)/g );
                                     my @values;
-                                    for (my $i = 0; $i < @subf; $i += 2) {
+                                    for ( my $i = 0 ; $i < @subf ; $i += 2 ) {
                                         if ( $letters =~ $subf[$i] ) {
-                                             my $value = $subf[$i+1];
-                                             $value =~ s/^ *//;
-                                             $value =~ s/ *$//;
-                                             push @values, $value;
+                                            my $value = $subf[ $i + 1 ];
+                                            $value =~ s/^ *//;
+                                            $value =~ s/ *$//;
+                                            push @values, $value;
                                         }
                                     }
-                                    my $data = join($facet->{sep}, @values);
+                                    my $data = join( $facet->{sep}, @values );
                                     unless ( $data ~~ @used_datas ) {
-                                        $facets_counter->{ $facet->{idx} }->{$data}++;
+                                        $facets_counter->{ $facet->{idx} }
+                                          ->{$data}++;
                                         push @used_datas, $data;
                                     }
-                                } # fields
-                            } # field codes
-                        } # records
-                        $facets_info->{ $facet->{idx} }->{label_value} = $facet->{label};
-                        $facets_info->{ $facet->{idx} }->{expanded} = $facet->{expanded};
-                    } # facets
+                                }    # fields
+                            }    # field codes
+                        }    # records
+                        $facets_info->{ $facet->{idx} }->{label_value} =
+                          $facet->{label};
+                        $facets_info->{ $facet->{idx} }->{expanded} =
+                          $facet->{expanded};
+                    }    # facets
                 }
-            }
 
-            # warn "connection ", $i-1, ": $size hits";
-            # warn $results[$i-1]->record(0)->render() if $size > 0;
+                # warn "connection ", $i-1, ": $size hits";
+                # warn $results[$i-1]->record(0)->render() if $size > 0;
 
-            # BUILD FACETS
-            if ( $servers[ $i - 1 ] =~ /biblioserver/ ) {
-                for my $link_value (
-                    sort { $facets_counter->{$b} <=> $facets_counter->{$a} }
-                        keys %$facets_counter )
-                {
-                    my $expandable;
-                    my $number_of_facets;
-                    my @this_facets_array;
-                    for my $one_facet (
-                        sort {
-                             $facets_counter->{$link_value}->{$b}
-                         <=> $facets_counter->{$link_value}->{$a}
-                        } keys %{ $facets_counter->{$link_value} }
+                # BUILD FACETS
+                if ( $servers[ $i - 1 ] =~ /biblioserver/ ) {
+                    for my $link_value (
+                        sort { $facets_counter->{$b} <=> $facets_counter->{$a} }
+                        keys %$facets_counter
                       )
                     {
-                        $number_of_facets++;
-                        if (   ( $number_of_facets < 6 )
-                            || ( $expanded_facet eq $link_value )
-                            || ( $facets_info->{$link_value}->{'expanded'} ) )
+                        my $expandable;
+                        my $number_of_facets;
+                        my @this_facets_array;
+                        for my $one_facet (
+                            sort {
+                                $facets_counter->{$link_value}
+                                  ->{$b} <=> $facets_counter->{$link_value}
+                                  ->{$a}
+                            } keys %{ $facets_counter->{$link_value} }
+                          )
                         {
+                            $number_of_facets++;
+                            if (   ( $number_of_facets < 6 )
+                                || ( $expanded_facet eq $link_value )
+                                || ( $facets_info->{$link_value}->{'expanded'} )
+                              )
+                            {
+
+# Sanitize the link value : parenthesis, question and exclamation mark will cause errors with CCL
+                                my $facet_link_value = $one_facet;
+                                $facet_link_value =~ s/[()!?¡¿؟]/ /g;
+
+                                # fix the length that will display in the label,
+                                my $facet_label_value = $one_facet;
+                                my $facet_max_length  = C4::Context->preference(
+                                    'FacetLabelTruncationLength')
+                                  || 20;
+                                $facet_label_value =
+                                  substr( $one_facet, 0, $facet_max_length )
+                                  . "..."
+                                  if length($facet_label_value) >
+                                      $facet_max_length;
 
-                      # Sanitize the link value ), ( will cause errors with CCL,
-                            my $facet_link_value = $one_facet;
-                            $facet_link_value =~ s/(\(|\))/ /g;
+                            # if it's a branch, label by the name, not the code,
+                                if ( $link_value =~ /branch/ ) {
+                                    if (   defined $branches
+                                        && ref($branches) eq "HASH"
+                                        && defined $branches->{$one_facet}
+                                        && ref( $branches->{$one_facet} ) eq
+                                        "HASH" )
+                                    {
+                                        $facet_label_value =
+                                          $branches->{$one_facet}
+                                          ->{'branchname'};
+                                    }
+                                    else {
+                                        $facet_label_value = "*";
+                                    }
+                                }
 
-                            # fix the length that will display in the label,
-                            my $facet_label_value = $one_facet;
-                            my $facet_max_length =
-                                C4::Context->preference('FacetLabelTruncationLength') || 20;
-                            $facet_label_value =
-                              substr( $one_facet, 0, $facet_max_length ) . "..."
-                                if length($facet_label_value) > $facet_max_length;
+                          # if it's a itemtype, label by the name, not the code,
+                                if ( $link_value =~ /itype/ ) {
+                                    if (   defined $itemtypes
+                                        && ref($itemtypes) eq "HASH"
+                                        && defined $itemtypes->{$one_facet}
+                                        && ref( $itemtypes->{$one_facet} ) eq
+                                        "HASH" )
+                                    {
+                                        $facet_label_value =
+                                          $itemtypes->{$one_facet}
+                                          ->{'description'};
+                                    }
+                                }
 
-                            # if it's a branch, label by the name, not the code,
-                            if ( $link_value =~ /branch/ ) {
-                                                               if (defined $branches
-                                                                       && ref($branches) eq "HASH"
-                                                                       && defined $branches->{$one_facet}
-                                                                       && ref ($branches->{$one_facet}) eq "HASH")
-                                                               {
-                                       $facet_label_value =
-                                               $branches->{$one_facet}->{'branchname'};
-                                                               }
-                                                               else {
-                                                                       $facet_label_value = "*";
-                                                               }
-                            }
-                            # if it's a itemtype, label by the name, not the code,
-                            if ( $link_value =~ /itype/ ) {
-                                if (defined $itemtypes
-                                    && ref($itemtypes) eq "HASH"
-                                    && defined $itemtypes->{$one_facet}
-                                    && ref ($itemtypes->{$one_facet}) eq "HASH")
-                                {
+               # also, if it's a location code, use the name instead of the code
+                                if ( $link_value =~ /location/ ) {
                                     $facet_label_value =
-                                        $itemtypes->{$one_facet}->{'description'};
+                                      GetKohaAuthorisedValueLib( 'LOC',
+                                        $one_facet, $opac );
                                 }
-                            }
 
-                            # also, if it's a location code, use the name instead of the code
-                            if ( $link_value =~ /location/ ) {
-                                $facet_label_value = GetKohaAuthorisedValueLib('LOC', $one_facet, $opac);
+                # but we're down with the whole label being in the link's title.
+                                push @this_facets_array,
+                                  {
+                                    facet_count =>
+                                      $facets_counter->{$link_value}
+                                      ->{$one_facet},
+                                    facet_label_value => $facet_label_value,
+                                    facet_title_value => $one_facet,
+                                    facet_link_value  => $facet_link_value,
+                                    type_link_value   => $link_value,
+                                  }
+                                  if ($facet_label_value);
                             }
-
-                            # but we're down with the whole label being in the link's title.
-                            push @this_facets_array, {
-                                facet_count       => $facets_counter->{$link_value}->{$one_facet},
-                                facet_label_value => $facet_label_value,
-                                facet_title_value => $one_facet,
-                                facet_link_value  => $facet_link_value,
-                                type_link_value   => $link_value,
-                            } if ( $facet_label_value );
                         }
-                    }
 
-                    # handle expanded option
-                    unless ( $facets_info->{$link_value}->{'expanded'} ) {
-                        $expandable = 1
-                          if ( ( $number_of_facets > 6 )
-                            && ( $expanded_facet ne $link_value ) );
+                        # handle expanded option
+                        unless ( $facets_info->{$link_value}->{'expanded'} ) {
+                            $expandable = 1
+                              if ( ( $number_of_facets > 6 )
+                                && ( $expanded_facet ne $link_value ) );
+                        }
+                        push @facets_loop,
+                          {
+                            type_link_value => $link_value,
+                            type_id         => $link_value . "_id",
+                            "type_label_"
+                              . $facets_info->{$link_value}->{'label_value'} =>
+                              1,
+                            facets     => \@this_facets_array,
+                            expandable => $expandable,
+                            expand     => $link_value,
+                          }
+                          unless (
+                            (
+                                $facets_info->{$link_value}->{'label_value'} =~
+                                /Libraries/
+                            )
+                            and ( C4::Context->preference('singleBranchMode') )
+                          );
                     }
-                    push @facets_loop, {
-                        type_link_value => $link_value,
-                        type_id         => $link_value . "_id",
-                        "type_label_" . $facets_info->{$link_value}->{'label_value'} => 1,
-                        facets     => \@this_facets_array,
-                        expandable => $expandable,
-                        expand     => $link_value,
-                    } unless ( ($facets_info->{$link_value}->{'label_value'} =~ /Libraries/) and (C4::Context->preference('singleBranchMode')) );
                 }
             }
-        }
-    }
+        );
     return ( undef, $results_hashref, \@facets_loop );
 }
 
@@ -1047,7 +1117,9 @@ on authority data).
 =cut
 
 sub _handle_exploding_index {
-    my ( $index, $term ) = @_;
+    my ($QParser, $struct, $filter, $params, $negate, $server) = @_;
+    my $index = $filter;
+    my $term = join(' ', @$params);
 
     return unless ($index =~ m/(su-br|su-na|su-rl)/ && $term);
 
@@ -1055,8 +1127,8 @@ sub _handle_exploding_index {
 
     my $codesubfield = $marcflavour eq 'UNIMARC' ? '5' : 'w';
     my $wantedcodes = '';
-    my @subqueries = ( "(su=\"$term\")");
-    my ($error, $results, $total_hits) = SimpleSearch( "Heading,wrdl=$term", undef, undef, [ "authorityserver" ] );
+    my @subqueries = ( "su:\"$term\"");
+    my ($error, $results, $total_hits) = SimpleSearch( "he:$term", undef, undef, [ "authorityserver" ] );
     foreach my $auth (@$results) {
         my $record = MARC::Record->new_from_usmarc($auth);
         my @references = $record->field('5..');
@@ -1070,11 +1142,13 @@ sub _handle_exploding_index {
             }
             foreach my $reference (@references) {
                 my $codes = $reference->subfield($codesubfield);
-                push @subqueries, '(su="' . $reference->as_string('abcdefghijlmnopqrstuvxyz') . '")' if (($codes && $codes eq $wantedcodes) || !$wantedcodes);
+                push @subqueries, 'su:"' . $reference->as_string('abcdefghijlmnopqrstuvxyz') . '"' if (($codes && $codes eq $wantedcodes) || !$wantedcodes);
             }
         }
     }
-    return join(' or ', @subqueries);
+    my $query = '(' x scalar(@subqueries) . join(') || ', @subqueries) . ')';
+    warn $query;
+    return $query;
 }
 
 =head2 parseQuery
@@ -1101,37 +1175,42 @@ sub parseQuery {
     my $query = $operands[0];
     my $index;
     my $term;
+    my $query_desc;
 
-# TODO: once we are using QueryParser, all this special case code for
-#       exploded search indexes will be replaced by a callback to
-#       _handle_exploding_index
-    if ( $query =~ m/^(.*)\b(su-br|su-na|su-rl)[:=](\w.*)$/ ) {
-        $query = $1;
-        $index = $2;
-        $term  = $3;
-    } else {
+    my $QParser;
+    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') || $query =~ s/^qp=//);
+    undef $QParser if ($query =~ m/^(ccl=|pqf=|cql=)/ || grep (/\w,\w|\w=\w/, @operands) );
+
+    if ($QParser)
+    {
         $query = '';
-        for ( my $i = 0 ; $i <= @operands ; $i++ ) {
-            if ($operands[$i] && $indexes[$i] =~ m/(su-br|su-na|su-rl)/) {
-                $index = $indexes[$i];
-                $term = $operands[$i];
-            } elsif ($operands[$i]) {
-                $query .= $operators[$i] eq 'or' ? ' or ' : ' and ' if ($query);
-                $query .= "($indexes[$i]:$operands[$i])";
-            }
+        for ( my $ii = 0 ; $ii <= @operands ; $ii++ ) {
+            next unless $operands[$ii];
+            $query .= $operators[ $ii - 1 ] eq 'or' ? ' || ' : ' && '
+              if ($query);
+            $query .=
+              ( $indexes[$ii] ? "$indexes[$ii]:" : '' ) . $operands[$ii];
+        }
+        foreach my $limit (@limits) {
+        }
+        foreach my $modifier (@sort_by) {
+            $query .= " #$modifier";
         }
-    }
 
-    if ($index) {
-        my $queryPart = _handle_exploding_index($index, $term);
-        if ($queryPart) {
-            $query .= "($queryPart)";
+        $query_desc = $query;
+        if ( C4::Context->preference("QueryWeightFields") ) {
         }
-        $operators = ();
-        $operands[0] = "ccl=$query";
+        $QParser->add_bib1_filter_map( 'biblioserver', 'su-br', { 'callback' => \&_handle_exploding_index });
+        $QParser->add_bib1_filter_map( 'biblioserver', 'su-na', { 'callback' => \&_handle_exploding_index });
+        $QParser->add_bib1_filter_map( 'biblioserver', 'su-rl', { 'callback' => \&_handle_exploding_index });
+        $QParser->parse( $query );
+        $operands[0] = "pqf=" . $QParser->target_syntax('biblioserver');
+# TODO: once we are using QueryParser, all this special case code for
+#       exploded search indexes will be replaced by a callback to
+#       _handle_exploding_index
     }
 
-    return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang);
+    return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc);
 }
 
 =head2 buildQuery
@@ -1155,7 +1234,8 @@ sub buildQuery {
 
     warn "---------\nEnter buildQuery\n---------" if $DEBUG;
 
-    ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang);
+    my $query_desc;
+    ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang);
 
     # dereference
     my @operators = $operators ? @$operators : ();
@@ -1183,7 +1263,6 @@ sub buildQuery {
 
     # initialize the variables we're passing back
     my $query_cgi;
-    my $query_desc;
     my $query_type;
 
     my $limit;
@@ -1218,7 +1297,13 @@ sub buildQuery {
         return ( undef, $', $', "q=cql=$'", $', '', '', '', '', 'cql' );
     }
     if ( $query =~ /^pqf=/ ) {
-        return ( undef, $', $', "q=pqf=$'", $', '', '', '', '', 'pqf' );
+        if ($query_desc) {
+            $query_cgi = "q=$query_desc";
+        } else {
+            $query_desc = $';
+            $query_cgi = "q=pqf=$'";
+        }
+        return ( undef, $', $', $query_cgi, $query_desc, '', '', '', '', 'pqf' );
     }
 
     # pass nested queries directly
@@ -1799,7 +1884,7 @@ sub searchResults {
             else {
 
                 # item is on order
-                if ( $item->{notforloan} == -1 ) {
+                if ( $item->{notforloan} < 0 ) {
                     $ordered_count++;
                 }
 
@@ -1808,8 +1893,7 @@ sub searchResults {
                 my ($transfertfrom, $transfertto);
 
                 # is item on the reserve shelf?
-               my $reservestatus = '';
-               my $reserveitem;
+                my $reservestatus = '';
 
                 unless ($item->{wthdrawn}
                         || $item->{itemlost}
@@ -1830,32 +1914,42 @@ sub searchResults {
                     #        should map transit status to record indexed in Zebra.
                     #
                     ($transfertwhen, $transfertfrom, $transfertto) = C4::Circulation::GetTransfers($item->{itemnumber});
-                   ($reservestatus, $reserveitem, undef) = C4::Reserves::CheckReserves($item->{itemnumber});
+                    $reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber}, $oldbiblio->{biblionumber} );
                 }
 
                 # item is withdrawn, lost, damaged, not for loan, reserved or in transit
                 if (   $item->{wthdrawn}
                     || $item->{itemlost}
                     || $item->{damaged}
-                    || $item->{notforloan} > 0
-                   || $reservestatus eq 'Waiting'
+                    || $item->{notforloan}
+                    || $reservestatus eq 'Waiting'
                     || ($transfertwhen ne ''))
                 {
                     $wthdrawn_count++        if $item->{wthdrawn};
                     $itemlost_count++        if $item->{itemlost};
                     $itemdamaged_count++     if $item->{damaged};
                     $item_in_transit_count++ if $transfertwhen ne '';
-                   $item_onhold_count++     if $reservestatus eq 'Waiting';
+                    $item_onhold_count++     if $reservestatus eq 'Waiting';
                     $item->{status} = $item->{wthdrawn} . "-" . $item->{itemlost} . "-" . $item->{damaged} . "-" . $item->{notforloan};
 
                     # can place hold on item ?
-                    if ((!$item->{damaged} || C4::Context->preference('AllowHoldsOnDamagedItems'))
-                      && !$item->{itemlost}
-                      && !$item->{withdrawn}
-                    ) {
-                        $can_place_holds = 1;
+                    if ( !$item->{itemlost} ) {
+                        if ( !$item->{wthdrawn} ){
+                            if ( $item->{damaged} ){
+                                if ( C4::Context->preference('AllowHoldsOnDamagedItems') ){
+                                    # can place a hold on a damaged item if AllowHoldsOnDamagedItems is true
+                                    if ( ( !$item->{notforloan} || $item->{notforloan} < 0 ) ){
+                                        # item is either for loan or has notforloan < 0
+                                        $can_place_holds = 1;
+                                    }
+                                }
+                            } elsif ( $item->{notforloan} < 0 ) {
+                                # item is not damaged and notforloan is < 0
+                                $can_place_holds = 1;
+                            }
+                        }
                     }
-                    
+
                     $other_count++;
 
                     my $key = $prefix . $item->{status};
@@ -2806,24 +2900,53 @@ sub GetDistinctValues {
                }
                # The big moment: asynchronously retrieve results from all servers
                my @elements;
-               while ( ( my $i = ZOOM::event( \@zconns ) ) != 0 ) {
-                       my $ev = $zconns[ $i - 1 ]->last_event();
-                       if ( $ev == ZOOM::Event::ZEND ) {
-                               next unless $results[ $i - 1 ];
-                               my $size = $results[ $i - 1 ]->size();
-                               if ( $size > 0 ) {
-                      for (my $j=0;$j<$size;$j++){
-                                               my %hashscan;
-                                               @hashscan{qw(value cnt)}=$results[ $i - 1 ]->display_term($j);
-                                               push @elements, \%hashscan;
-                                         }
-                               }
-                       }
-               }
+        _ZOOM_event_loop(
+            \@zconns,
+            \@results,
+            sub {
+                my ( $i, $size ) = @_;
+                for ( my $j = 0 ; $j < $size ; $j++ ) {
+                    my %hashscan;
+                    @hashscan{qw(value cnt)} =
+                      $results[ $i - 1 ]->display_term($j);
+                    push @elements, \%hashscan;
+                }
+            }
+        );
                return \@elements;
    }
 }
 
+=head2 _ZOOM_event_loop
+
+    _ZOOM_event_loop(\@zconns, \@results, sub {
+        my ( $i, $size ) = @_;
+        ....
+    } );
+
+Processes a ZOOM event loop and passes control to a closure for
+processing the results, and destroying the resultsets.
+
+=cut
+
+sub _ZOOM_event_loop {
+    my ($zconns, $results, $callback) = @_;
+    while ( ( my $i = ZOOM::event( $zconns ) ) != 0 ) {
+        my $ev = $zconns->[ $i - 1 ]->last_event();
+        if ( $ev == ZOOM::Event::ZEND ) {
+            next unless $results->[ $i - 1 ];
+            my $size = $results->[ $i - 1 ]->size();
+            if ( $size > 0 ) {
+                $callback->($i, $size);
+            }
+        }
+    }
+
+    foreach my $result (@$results) {
+        $result->destroy();
+    }
+}
+
 
 END { }    # module clean-up code here (global destructor)