X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FSearch.pm;h=14935ab731e2790e3bab502e1fa71b5a8cace678;hb=eded6edacc5e3bf8dd0be21ed05842c3b78aadce;hp=65f514498fc62546972bb0844cd7cc7628cc24a2;hpb=d4468c5f33f03cbd616527a558c1b67670089642;p=koha-ffzg.git diff --git a/C4/Search.pm b/C4/Search.pm index 65f514498f..14935ab731 100644 --- a/C4/Search.pm +++ b/C4/Search.pm @@ -15,34 +15,45 @@ package C4::Search; # You should have received a copy of the GNU General Public License # along with Koha; if not, see . -use strict; -#use warnings; FIXME - Bug 2505 -require Exporter; +use Modern::Perl; 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 YAML; +use Koha::Recalls; +use Koha::RecordProcessor; 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 +72,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 @@ -85,12 +85,9 @@ 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; - my $search; - my $type; - my ( $biblionumber, $title ); # search duplicate on ISBN, easy and fast.. # ... normalize first @@ -100,31 +97,16 @@ sub FindDuplicate { $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 = '&&'; - $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate'); - } else { - $titleindex = 'ti,ext'; - $authorindex = 'au,ext'; - $op = 'and'; - } + + my $titleindex = 'ti,ext'; + my $authorindex = 'au,ext'; + my $op = 'AND'; $result->{title} =~ s /\\//g; $result->{title} =~ s /\"//g; $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; @@ -132,13 +114,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}) { @@ -147,7 +128,7 @@ sub FindDuplicate { $possible_duplicate_record ); - my $result = TransformMarcToKoha( $marcrecord, '' ); + my $result = TransformMarcToKoha({ record => $marcrecord }); # FIXME :: why 2 $biblionumber ? if ($result) { @@ -204,7 +185,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, { @@ -234,25 +215,12 @@ 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/)); - if ($QParser) { - $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate'); - } - # Initialize & Search Zebra for ( my $i = 0 ; $i < @servers ; $i++ ) { eval { $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 ); - if ($QParser) { - $query =~ s/=/:/g unless $options{skip_normalize}; - $QParser->parse( $query ); - $query = $QParser->target_syntax($servers[$i]); - $zoom_queries[$i] = new ZOOM::Query::PQF( $query, $zconns[$i]); - } else { - $query =~ s/:/=/g unless $options{skip_normalize}; - $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]); - } + $query =~ s/:/=/g unless $options{skip_normalize}; + $zoom_queries[$i] = ZOOM::Query::CCL2RPN->new( $query, $zconns[$i]); $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] ); # error handling @@ -335,13 +303,12 @@ 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 = (); # TODO simplify this structure ( { branchcode => $branchname } is enought) and remove this parameter - $branches ||= { map { $_->branchcode => { branchname => $_->branchname } } Koha::Libraries->search }; + $branches ||= { map { $_->branchcode => { branchname => $_->branchname } } Koha::Libraries->search->as_list }; # Initialize variables for the faceted results objects my $facets_counter = {}; @@ -358,26 +325,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 ($@) { @@ -424,6 +390,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 {$b} <=> $facets_counter->{$a} } - keys %$facets_counter + sort { $a cmp $b } keys %$facets_counter ) { my @this_facets_array; @@ -623,9 +593,11 @@ sub getRecords { # This sorts the facets into alphabetical order if (@facets_loop) { foreach my $f (@facets_loop) { - $f->{facets} = [ sort { uc($a->{facet_label_value}) cmp uc($b->{facet_label_value}) } @{ $f->{facets} } ]; + if( C4::Context->preference('FacetOrder') eq 'Alphabetical' ){ + $f->{facets} = + [ sort { uc($a->{facet_label_value}) cmp uc($b->{facet_label_value}) } @{ $f->{facets} } ]; + } } - @facets_loop = sort {$a->{expand} cmp $b->{expand}} @facets_loop; } return ( undef, $results_hashref, \@facets_loop ); @@ -712,8 +684,9 @@ 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*(?{ $facet->{ idx } }->{ $data }++; } @@ -810,8 +783,9 @@ sub _get_facet_from_result_set { my $facets = {}; foreach my $term ( @terms ) { my $facet_value = $term->textContent; + $facet_value =~ s/\s*(?{ $facet_value } = $term->getAttribute( 'occur' ); + $facets->{ $facet_value } += $term->getAttribute( 'occur' ); } return $facets; @@ -839,80 +813,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 ) = @_; @@ -979,7 +879,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; } @@ -994,7 +895,7 @@ sub _build_weighted_query { my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0; $operand =~ s/"/ /g; # Bug 7518: searches with quotation marks don't work - my $weighted_query .= "(rk=("; # Specifies that we're applying rank + my $weighted_query = "(rk=("; # Specifies that we're applying rank # Keyword, or, no index specified if ( ( $index eq 'kw' ) || ( !$index ) ) { @@ -1105,6 +1006,8 @@ sub getIndexes{ 'Conference-name-seealso', 'Content-type', 'Control-number', + 'Control-number-identifier', + 'cni', 'copydate', 'Corporate-name', 'Corporate-name-heading', @@ -1169,6 +1072,8 @@ sub getIndexes{ 'mc-itemtype', 'mc-rtype', 'mus', + 'Multipart-resource-level', + 'mrl', 'name', 'Music-number', 'Name-geographic', @@ -1278,134 +1183,6 @@ sub getIndexes{ return \@indexes; } -=head2 _handle_exploding_index - - my $query = _handle_exploding_index($index, $term) - -Callback routine to generate the search for "exploding" indexes (i.e. -those indexes which are turned into multiple or-connected searches based -on authority data). - -=cut - -sub _handle_exploding_index { - my ($QParser, $filter, $params, $negate, $server) = @_; - my $index = $filter; - my $term = join(' ', @$params); - - return unless ($index =~ m/(su-br|su-na|su-rl)/ && $term); - - my $marcflavour = C4::Context->preference('marcflavour'); - - my $codesubfield = $marcflavour eq 'UNIMARC' ? '5' : 'w'; - my $wantedcodes = ''; - my @subqueries = ( "\@attr 1=Subject \@attr 4=1 \"$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..'); - if (@references) { - if ($index eq 'su-br') { - $wantedcodes = 'g'; - } elsif ($index eq 'su-na') { - $wantedcodes = 'h'; - } elsif ($index eq 'su-rl') { - $wantedcodes = ''; - } - foreach my $reference (@references) { - my $codes = $reference->subfield($codesubfield); - push @subqueries, '@attr 1=Subject @attr 4=1 "' . $reference->as_string('abcdefghijlmnopqrstuvxyz') . '"' if (($codes && $codes eq $wantedcodes) || !$wantedcodes); - } - } - } - my $query = ' @or ' x (scalar(@subqueries) - 1) . join(' ', @subqueries); - return $query; -} - -=head2 parseQuery - - ( $operators, $operands, $indexes, $limits, - $sort_by, $scan, $lang ) = - parseQuery ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang); - -Shim function to ease the transition from buildQuery to a new QueryParser. -This function is called at the beginning of buildQuery, and modifies -buildQuery's input. If it can handle the input, it returns a query that -buildQuery will not try to parse. - -=cut - -sub parseQuery { - my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_; - - my @operators = $operators ? @$operators : (); - my @indexes = $indexes ? @$indexes : (); - my @operands = $operands ? @$operands : (); - my @limits = $limits ? @$limits : (); - my @sort_by = $sort_by ? @$sort_by : (); - - my $query = $operands[0]; - my $index; - my $term; - my $query_desc; - - 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, @indexes) ); - undef $QParser if (scalar @limits > 0); - - if ($QParser) - { - $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate'); - $query = ''; - for ( my $ii = 0 ; $ii <= @operands ; $ii++ ) { - next unless $operands[$ii]; - $query .= $operators[ $ii - 1 ] eq 'or' ? ' || ' : ' && ' - if ($query); - if ( $operands[$ii] =~ /^[^"]\W*[-|_\w]*:\w.*[^"]$/ ) { - $query .= $operands[$ii]; - } - elsif ( $indexes[$ii] =~ m/su-/ ) { - $query .= $indexes[$ii] . '(' . $operands[$ii] . ')'; - } - else { - $query .= - ( $indexes[$ii] ? "$indexes[$ii]:" : '' ) . $operands[$ii]; - } - } - foreach my $limit (@limits) { - } - if ( scalar(@sort_by) > 0 ) { - my $modifier_re = - '#(' . join( '|', @{ $QParser->modifiers } ) . ')'; - $query =~ s/$modifier_re//g; - foreach my $modifier (@sort_by) { - $query .= " #$modifier"; - } - } - - $query_desc = $query; - $query_desc =~ s/\s+/ /g; - if ( C4::Context->preference("QueryWeightFields") ) { - } - $QParser->add_bib1_filter_map( 'su-br' => 'biblioserver' => - { 'target_syntax_callback' => \&_handle_exploding_index } ); - $QParser->add_bib1_filter_map( 'su-na' => 'biblioserver' => - { 'target_syntax_callback' => \&_handle_exploding_index } ); - $QParser->add_bib1_filter_map( 'su-rl' => 'biblioserver' => - { 'target_syntax_callback' => \&_handle_exploding_index } ); - $QParser->parse($query); - $operands[0] = "pqf=" . $QParser->target_syntax('biblioserver'); - } - else { - require Koha::QueryParser::Driver::PQF; - my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')'; - s/$modifier_re//g for @operands; - } - - return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc); -} - =head2 buildQuery ( $error, $query, @@ -1425,10 +1202,7 @@ See verbose embedded documentation. sub buildQuery { my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_; - warn "---------\nEnter buildQuery\n---------" if $DEBUG; - 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 : (); @@ -1442,14 +1216,14 @@ 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 my $query_cgi; my $query_type; - my $limit; + my $limit = q{}; my $limit_cgi; my $limit_desc; @@ -1463,37 +1237,112 @@ 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:' ) { + $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 = $search_filter->expand_filter; + my ( $error, undef, undef, undef, undef, $fixed_limit, undef, undef, undef ) = buildQuery ( undef, undef, undef, $expanded, 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 { /^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 { /^available$/ } @limits ) { - $q .= q| and ( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) )|; - delete $limits['available']; - } - $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=/ ) { return ( undef, $', $', "q=cql=".uri_escape_utf8($'), $', '', '', '', 'cql' ); } if ( $query =~ /^pqf=/ ) { - if ($query_desc) { - $query_cgi = "q=".uri_escape_utf8($query_desc); - } else { - $query_desc = $'; - $query_cgi = "q=pqf=".uri_escape_utf8($'); - } + $query_desc = $'; + $query_cgi = "q=pqf=".uri_escape_utf8($'); return ( undef, $', $', $query_cgi, $query_desc, '', '', '', 'pqf' ); } @@ -1537,11 +1386,11 @@ sub buildQuery { $operands[$i] =~ s/\?/{?}/g; # need to escape question marks } my $operand = $operands[$i]; - my $index = $indexes[$i]; + my $index = $indexes[$i] || 'kw'; # 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. @@ -1573,19 +1422,22 @@ 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) . ')'; - $indexes[$i] = $index = ''; + $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'; } } - } - - if(not $index){ - $index = 'kw'; } # Set default structure attribute (word list) my $struct_attr = q{}; - unless ( $indexes_set || !$index || $index =~ /,(st-|phr|ext|wrdl)/ || $index =~ /^(nb|ns)$/ ) { + unless ( $indexes_set || $index =~ /,(st-|phr|ext|wrdl)/ || $index =~ /^(nb|ns)$/ ) { $struct_attr = ",wrdl"; } @@ -1599,18 +1451,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 ( @@ -1643,24 +1494,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, @@ -1678,67 +1536,8 @@ sub buildQuery { } #/if $operands } # /for } - warn "QUERY BEFORE LIMITS: >$query<" if $DEBUG; - - # 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."'"; - } + Koha::Logger->get->debug("QUERY BEFORE LIMITS: >$query<"); - $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"; - } - - # Regular old limits - else { - $limit .= " and " if $limit || $query; - $limit .= "$this_limit"; - $limit_cgi .= "&limit=" . uri_escape_utf8($this_limit); - if ($this_limit =~ /^branch:(.+)/) { - my $branchcode = $1; - my $library = Koha::Libraries->find( $branchcode ); - if (defined $library) { - $limit_desc .= " branch:" . $library->branchname; - } else { - $limit_desc .= " $this_limit"; - } - } else { - $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 @@ -1751,6 +1550,15 @@ sub buildQuery { $query =~ s/(?<=(st-numeric)):/=/g; $query =~ s/(?<=(st-year)):/=/g; $query =~ s/(?<=(st-date-normalized)):/=/g; + + # Removing warnings for later substitutions + $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 @@ -1766,16 +1574,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, @@ -1798,7 +1599,7 @@ sub _build_initial_query { my $operator = ""; if ($params->{previous_operand}){ #If there is a previous operand, add a supplied operator or the default 'and' - $operator = ($params->{operator}) ? " ".($params->{operator})." " : ' and '; + $operator = ($params->{operator}) ? ($params->{operator}) : 'AND'; } #NOTE: indexes_set is typically set when doing truncation or field weighting @@ -1806,14 +1607,14 @@ sub _build_initial_query { #e.g. "kw,wrdl:test" #e.g. " and kw,wrdl:test" - $params->{query} .= $operator . $operand; + $params->{query} .= " " . $operator . " " . $operand; $params->{query_cgi} .= "&op=".uri_escape_utf8($operator) if $operator; $params->{query_cgi} .= "&idx=".uri_escape_utf8($params->{index}) if $params->{index}; - $params->{query_cgi} .= "&q=".uri_escape_utf8($params->{original_operand}) if $params->{original_operand}; + $params->{query_cgi} .= "&q=".uri_escape_utf8($params->{original_operand}) if ( $params->{original_operand} ne '' ); #e.g. " and kw,wrdl: test" - $params->{query_desc} .= $operator . $params->{index_plus} . " " . $params->{original_operand}; + $params->{query_desc} .= " " . $operator . " " . ( $params->{index_plus} // q{} ) . " " . ( $params->{original_operand} // q{} ); $params->{previous_operand} = 1 unless $params->{previous_operand}; #If there is no previous operand, mark this as one @@ -1833,7 +1634,7 @@ Format results in a form suitable for passing to the template # IMO this subroutine is pretty messy still -- it's responsible for # building the HTML output for the template sub searchResults { - my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults ) = @_; + my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults, $xslt_variables ) = @_; my $dbh = C4::Context->dbh; my @newresults; @@ -1846,8 +1647,12 @@ sub searchResults { $is_opac = 1; } + my $record_processor = Koha::RecordProcessor->new({ + filters => 'ViewPolicy' + }); + #Build branchnames hash - my %branches = map { $_->branchcode => $_->branchname } Koha::Libraries->search({}, { order_by => 'branchname' }); + my %branches = map { $_->branchcode => $_->branchname } Koha::Libraries->search({}, { order_by => 'branchname' })->as_list; # FIXME - We build an authorised values hash here, using the default framework # though it is possible to have different authvals for different fws. @@ -1878,12 +1683,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"); @@ -1891,17 +1696,6 @@ sub searchResults { my ($bibliotag,$bibliosubf)=GetMarcFromKohaField( 'biblio.biblionumber' ); # set stuff for XSLT processing here once, not later again for every record we retrieved - my $xslfile; - my $xslsyspref; - if( $is_opac ){ - $xslsyspref = "OPACXSLTResultsDisplay"; - $xslfile = C4::Context->preference( $xslsyspref ); - } else { - $xslsyspref = "XSLTResultsDisplay"; - $xslfile = C4::Context->preference( $xslsyspref ) || "default"; - } - my $lang = $xslfile ? C4::Languages::getlanguage() : undef; - my $sysxml = $xslfile ? C4::XSLT::get_xslt_sysprefs() : undef; my $userenv = C4::Context->userenv; my $logged_in_user @@ -1937,74 +1731,33 @@ sub searchResults { : GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf)); SetUTF8Flag($marcrecord); - my $oldbiblio = TransformMarcToKoha( $marcrecord, $fw ); + my $oldbiblio = TransformMarcToKoha({ record => $marcrecord, limit_table => 'no_items' }); $oldbiblio->{result_number} = $i + 1; - # add imageurl to itemtype if there is one - $oldbiblio->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} ); - $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 $oldbiblio->{edition} = $oldbiblio->{editionstatement}; - $oldbiblio->{description} = $itemtypes{ $oldbiblio->{itemtype} }->{translated_description}; - # Build summary if there is one (the summary is defined in the itemtypes table) - # FIXME: is this used anywhere, I think it can be commented out? -- JF - if ( $itemtypes{ $oldbiblio->{itemtype} }->{summary} ) { - my $summary = $itemtypes{ $oldbiblio->{itemtype} }->{summary}; - my @fields = $marcrecord->fields(); - - my $newsummary; - foreach my $line ( "$summary\n" =~ /(.*)\n/g ){ - my $tags = {}; - foreach my $tag ( $line =~ /\[(\d{3}[\w|\d])\]/ ) { - $tag =~ /(.{3})(.)/; - if($marcrecord->field($1)){ - my @abc = $marcrecord->field($1)->subfield($2); - $tags->{$tag} = $#abc + 1 ; - } - } - - # We catch how many times to repeat this line - my $max = 0; - foreach my $tag (keys(%$tags)){ - $max = $tags->{$tag} if($tags->{$tag} > $max); - } - - # we replace, and repeat each line - for (my $i = 0 ; $i < $max ; $i++){ - my $newline = $line; - - foreach my $tag ( $newline =~ /\[(\d{3}[\w|\d])\]/g ) { - $tag =~ /(.{3})(.)/; - if($marcrecord->field($1)){ - my @repl = $marcrecord->field($1)->subfield($2); - my $subfieldvalue = $repl[$i]; - $newline =~ s/\[$tag\]/$subfieldvalue/g; - } - } - $newsummary .= "$newline\n"; - } - } - - $newsummary =~ s/\[(.*?)]//g; - $newsummary =~ s/\n//g; - $oldbiblio->{summary} = $newsummary; - } + my $itemtype = $oldbiblio->{itemtype} ? $itemtypes{$oldbiblio->{itemtype}} : undef; + # add imageurl to itemtype if there is one + $oldbiblio->{imageurl} = $itemtype ? getitemtypeimagelocation( $search_context->{'interface'}, $itemtype->{imageurl} ) : q{}; + # Build summary if there is one (the summary is defined in the itemtypes table) + $oldbiblio->{description} = $itemtype ? $itemtype->{translated_description} : q{}; # Pull out the items fields my @fields = $marcrecord->field($itemtag); + $marcrecord->delete_fields( @fields ) unless C4::Context->preference('PassItemMarcToXSLT'); my $marcflavor = C4::Context->preference("marcflavour"); # adding linked items that belong to host records if ( C4::Context->preference('EasyAnalyticalRecords') ) { my $analyticsfield = '773'; - if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') { + if ($marcflavor eq 'MARC21') { $analyticsfield = '773'; } elsif ($marcflavor eq 'UNIMARC') { $analyticsfield = '461'; @@ -2013,18 +1766,11 @@ sub searchResults { my $hostbiblionumber = $hostfield->subfield("0"); my $linkeditemnumber = $hostfield->subfield("9"); if( $hostbiblionumber ) { - my $hostbiblio = GetMarcBiblio({ - biblionumber => $hostbiblionumber, - embed_items => 1 }); - my ($itemfield, undef) = GetMarcFromKohaField( 'items.itemnumber' ); - if( $hostbiblio ) { - my @hostitems = $hostbiblio->field($itemfield); - foreach my $hostitem (@hostitems){ - if ($hostitem->subfield("9") eq $linkeditemnumber){ - my $linkeditem =$hostitem; - # append linked items if they exist - push @fields, $linkeditem if $linkeditem; - } + my $linkeditemmarc = C4::Items::GetMarcItem( $hostbiblionumber, $linkeditemnumber ); + if ($linkeditemmarc) { + my $linkeditemfield = $linkeditemmarc->field($itemtag); + if ($linkeditemfield) { + push( @fields, $linkeditemfield ); } } } @@ -2051,9 +1797,9 @@ sub searchResults { my $itembinding_count = 0; my $itemdamaged_count = 0; my $item_in_transit_count = 0; - my $can_place_holds = 0; my $item_onhold_count = 0; my $notforloan_count = 0; + my $item_recalled_count = 0; my $items_count = scalar(@fields); my $maxitems_pref = C4::Context->preference('maxItemsinSearchResults'); my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1; @@ -2067,19 +1813,21 @@ sub searchResults { foreach my $code ( keys %subfieldstosearch ) { $item->{$code} = $field->subfield( $subfieldstosearch{$code} ); } - $item->{description} = $itemtypes{ $item->{itype} }{translated_description}; - # OPAC hidden items + 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 if ($is_opac) { - # hidden because lost - if ($hidelostitems && $item->{itemlost}) { - $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; + # 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; } @@ -2096,7 +1844,11 @@ sub searchResults { $item->{'branchname'} = $branches{$item->{$otherbranch}}; } - my $prefix = $item->{$hbranch} . '--' . $item->{location} . $item->{itype} . $item->{itemcallnumber}; + my $prefix = + ( $item->{$hbranch} ? $item->{$hbranch} . '--' : q{} ) + . ( $item->{location} ? $item->{location} : q{} ) + . ( $item->{itype} ? $item->{itype} : q{} ) + . ( $item->{itemcallnumber} ? $item->{itemcallnumber} : q{} ); # For each grouping of items (onloan, available, unavailable), we build a key to store relevant info about that item if ( $item->{onloan} and $logged_in_user @@ -2107,7 +1859,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} = @@ -2118,15 +1870,14 @@ sub searchResults { $onloan_items->{$key}->{longoverdue}++; $longoverdue_count++; } - else { # can place holds as long as item isn't lost - $can_place_holds = 1; - } } # items not on loan, but still unavailable ( lost, withdrawn, damaged ) else { - $item->{notforloan}=1 if !$item->{notforloan} && $itemtypes{ C4::Context->preference("item-level_itypes")? $item->{itype}: $oldbiblio->{itemtype} }->{notforloan}; + my $itemtype = C4::Context->preference("item-level_itypes")? $item->{itype}: $oldbiblio->{itemtype}; + $item->{notforloan} = 1 if !$item->{notforloan} && + $itemtype && $itemtypes{ $itemtype }->{notforloan}; # item is on order if ( $item->{notforloan} < 0 ) { @@ -2142,6 +1893,9 @@ sub searchResults { # is item on the reserve shelf? my $reservestatus = ''; + # is item a waiting recall? + my $recallstatus = ''; + unless ($item->{withdrawn} || $item->{itemlost} || $item->{damaged} @@ -2160,9 +1914,22 @@ 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 ) { + $recallstatus = 'Waiting'; + } + } } # item is withdrawn, lost, damaged, not for loan, reserved or in transit @@ -2171,26 +1938,16 @@ sub searchResults { || $item->{damaged} || $item->{notforloan} || $reservestatus eq 'Waiting' - || ($transfertwhen ne '')) + || $recallstatus eq 'Waiting' + || ($transfertwhen && $transfertwhen ne '')) { $withdrawn_count++ if $item->{withdrawn}; $itemlost_count++ if $item->{itemlost}; $itemdamaged_count++ if $item->{damaged}; - $item_in_transit_count++ if $transfertwhen ne ''; + $item_in_transit_count++ if $transfertwhen && $transfertwhen ne ''; $item_onhold_count++ if $reservestatus eq 'Waiting'; - $item->{status} = $item->{withdrawn} . "-" . $item->{itemlost} . "-" . $item->{damaged} . "-" . $item->{notforloan}; - - # can place a hold on a item if - # not lost nor withdrawn - # not damaged unless AllowHoldsOnDamagedItems is true - # item is either for loan or on order (notforloan < 0) - $can_place_holds = 1 - if ( - !$item->{itemlost} - && !$item->{withdrawn} - && ( !$item->{damaged} || C4::Context->preference('AllowHoldsOnDamagedItems') ) - && ( !$item->{notforloan} || $item->{notforloan} < 0 ) - ); + $item_recalled_count++ if $recallstatus eq 'Waiting'; + $item->{status} = ($item->{withdrawn}//q{}) . "-" . ($item->{itemlost}//q{}) . "-" . ($item->{damaged}//q{}) . "-" . ($item->{notforloan}//q{}); $other_count++; @@ -2199,29 +1956,29 @@ sub searchResults { $other_items->{$key}->{$_} = $item->{$_}; } $other_items->{$key}->{intransit} = ( $transfertwhen ne '' ) ? 1 : 0; + $other_items->{$key}->{recalled} = ($recallstatus) ? 1 : 0; $other_items->{$key}->{onhold} = ($reservestatus) ? 1 : 0; $other_items->{$key}->{notforloan} = GetAuthorisedValueDesc('','',$item->{notforloan},'','',$notforloan_authorised_value) if $notforloan_authorised_value and $item->{notforloan}; $other_items->{$key}->{count}++ if $item->{$hbranch}; - $other_items->{$key}->{location} = $shelflocations->{ $item->{location} }; + $other_items->{$key}->{location} = $shelflocations->{ $item->{location} } if $item->{location}; $other_items->{$key}->{description} = $item->{description}; - $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context->{'interface'}, $itemtypes{ $item->{itype} }->{imageurl} ); + $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context->{'interface'}, $itemtypes{ $item->{itype}//q{} }->{imageurl} ); } # item is available else { - $can_place_holds = 1; $available_count++; $available_items->{$prefix}->{count}++ if $item->{$hbranch}; foreach (qw(branchname itemcallnumber description)) { $available_items->{$prefix}->{$_} = $item->{$_}; } - $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} }; - $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context->{'interface'}, $itemtypes{ $item->{itype} }->{imageurl} ); + $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} } if $item->{location}; + $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context->{'interface'}, $itemtypes{ $item->{itype}//q{} }->{imageurl} ); } } } # 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; } @@ -2241,15 +1998,41 @@ sub searchResults { # XSLT processing of some stuff # we fetched the sysprefs already before the loop through all retrieved record! - if (!$scan && $xslfile) { - $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, $xslsyspref, 1, \@hiddenitems, $sysxml, $xslfile, $lang); + if (!$scan) { + $record_processor->options({ + frameworkcode => $fw, + interface => $search_context->{'interface'} + }); + + $record_processor->process($marcrecord); + + $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display( + { + biblionumber => $oldbiblio->{biblionumber}, + record => $marcrecord, + xsl_syspref => ( + $is_opac + ? 'OPACXSLTResultsDisplay' + : 'XSLTResultsDisplay' + ), + fix_amps => 1, + hidden_items => \@hiddenitems, + xslt_variables => $xslt_variables + } + ); } + my $biblio_object = Koha::Biblios->find( $oldbiblio->{biblionumber} ); + $oldbiblio->{biblio_object} = $biblio_object; + + my $can_place_holds = 1; # if biblio level itypes are used and itemtype is notforloan, it can't be reserved either if (!C4::Context->preference("item-level_itypes")) { - if ($itemtypes{ $oldbiblio->{itemtype} }->{notforloan}) { + if ($itemtype && $itemtype->{notforloan}) { $can_place_holds = 0; } + } else { + $can_place_holds = $biblio_object->items->filter_by_for_hold()->count if $biblio_object; } $oldbiblio->{norequests} = 1 unless $can_place_holds; $oldbiblio->{items_count} = $items_count; @@ -2267,6 +2050,7 @@ sub searchResults { $oldbiblio->{damagedcount} = $itemdamaged_count; $oldbiblio->{intransitcount} = $item_in_transit_count; $oldbiblio->{onholdcount} = $item_onhold_count; + $oldbiblio->{recalledcount} = $item_recalled_count; $oldbiblio->{orderedcount} = $ordered_count; $oldbiblio->{notforloancount} = $notforloan_count; @@ -2419,7 +2203,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({});