X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FSearch.pm;h=1326f692bb2efc024790a4e39af88fc8de365942;hb=1d7622a90ff73280e5e8bfa720963526c4ab2c0a;hp=0453b91424679e43763c2dade4a7419c67b60c29;hpb=dbaefb626ce5c2022b647fc15e6d0a0d68fa0784;p=koha.git diff --git a/C4/Search.pm b/C4/Search.pm index 0453b91424..1326f692bb 100644 --- a/C4/Search.pm +++ b/C4/Search.pm @@ -68,7 +68,6 @@ This module provides searching functions for Koha's bibliographic databases &searchResults &getRecords &buildQuery - &AddSearchHistory &GetDistinctValues &enabled_staff_search_views &PurgeSearchHistory @@ -144,8 +143,11 @@ sub FindDuplicate { my @results; if (!defined $error) { foreach my $possible_duplicate_record (@{$searchresults}) { - my $marcrecord = - MARC::Record->new_from_usmarc($possible_duplicate_record); + my $marcrecord = new_record_from_zebra( + 'biblioserver', + $possible_duplicate_record + ); + my $result = TransformMarcToKoha( $dbh, $marcrecord, '' ); # FIXME :: why 2 $biblionumber ? @@ -289,10 +291,11 @@ sub SimpleSearch { } for my $j ( $first_record .. $last_record ) { - my $record = + my $record = eval { $tmpresults[ $i - 1 ]->record( $j - 1 )->raw() ; # 0 indexed - push @{$results}, $record; + }; + push @{$results}, $record if defined $record; } } ); @@ -337,10 +340,9 @@ sub getRecords { my $results_hashref = (); # Initialize variables for the faceted results objects - my $facets_counter = (); - my $facets_info = (); + my $facets_counter = {}; + my $facets_info = {}; my $facets = getFacets(); - my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets')||20; my @facets_loop; # stores the ref to array of hashes for template facets loop @@ -422,7 +424,7 @@ sub getRecords { warn "Ignoring unrecognized sort '$sort' requested" if $sort_by; } } - if ($sort_by && !$scan) { + if ( $sort_by && !$scan && $results[$i] ) { if ( $results[$i]->sort( "yaz", $sort_by ) < 0 ) { warn "WARNING sort $sort_by failed"; } @@ -446,13 +448,14 @@ sub getRecords { else { $times = $size; } + for ( my $j = $offset ; $j < $times ; $j++ ) { my $records_hash; my $record; ## Check if it's an index scan if ($scan) { - my ( $term, $occ ) = $results[ $i - 1 ]->term($j); + my ( $term, $occ ) = $results[ $i - 1 ]->display_term($j); # here we create a minimal MARC record and hand it off to the # template just like a normal result ... perhaps not ideal, but @@ -488,7 +491,6 @@ sub getRecords { # not an index scan else { $record = $results[ $i - 1 ]->record($j)->raw(); - # warn "RECORD $j:".$record; $results_hash->{'RECORDS'}[$j] = $record; } @@ -496,58 +498,13 @@ 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 @used_datas = (); - 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 ); - foreach my $field_token (@field_tokens) { - my @subf = ( $field_token =~ - /\$([a-zA-Z0-9]) ([^\$]+)/g ); - my @values; - 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 $data = join( $facet->{sep}, @values ); - unless ( $data ~~ @used_datas ) { - $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 + $facets_counter = GetFacets( $results[ $i - 1 ] ); + $facets_info = _get_facets_info( $facets ); } - # 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 ( @@ -567,7 +524,7 @@ sub getRecords { ) { $number_of_facets++; - if ( ( $number_of_facets < 6 ) + if ( ( $number_of_facets <= 5 ) || ( $expanded_facet eq $link_value ) || ( $facets_info->{$link_value}->{'expanded'} ) ) @@ -644,7 +601,7 @@ sub getRecords { # handle expanded option unless ( $facets_info->{$link_value}->{'expanded'} ) { $expandable = 1 - if ( ( $number_of_facets > 6 ) + if ( ( $number_of_facets > 5 ) && ( $expanded_facet ne $link_value ) ); } push @facets_loop, @@ -672,6 +629,217 @@ sub getRecords { return ( undef, $results_hashref, \@facets_loop ); } +sub GetFacets { + + my $rs = shift; + my $facets; + + my $indexing_mode = C4::Context->config('zebra_bib_index_mode') // 'dom'; + my $use_zebra_facets = C4::Context->config('use_zebra_facets') // 0; + + if ( $indexing_mode eq 'dom' && + $use_zebra_facets ) { + $facets = _get_facets_from_zebra( $rs ); + } else { + $facets = _get_facets_from_records( $rs ); + } + + return $facets; +} + +sub _get_facets_from_records { + + my $rs = shift; + + my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets') // 20; + my $facets_config = getFacets(); + my $facets = {}; + my $size = $rs->size(); + my $jmax = $size > $facets_maxrecs + ? $facets_maxrecs + : $size; + + for ( my $j = 0 ; $j < $jmax ; $j++ ) { + + my $marc_record = new_record_from_zebra ( + 'biblioserver', + $rs->record( $j )->raw() + ); + + if ( ! defined $marc_record ) { + warn "ERROR DECODING RECORD - $@: " . + $rs->record( $j )->raw(); + next; + } + + _get_facets_data_from_record( $marc_record, $facets_config, $facets ); + } + + return $facets; +} + +=head2 _get_facets_data_from_record + + C4::Search::_get_facets_data_from_record( $marc_record, $facets, $facets_counter ); + +Internal function that extracts facets information from a MARC::Record object +and populates $facets_counter for using in getRecords. + +$facets is expected to be filled with C4::Koha::getFacets output (i.e. the configured +facets for Zebra). + +=cut + +sub _get_facets_data_from_record { + + my ( $marc_record, $facets, $facets_counter ) = @_; + + for my $facet (@$facets) { + + my @used_datas = (); + + foreach my $tag ( @{ $facet->{ tags } } ) { + + # tag number is the first three digits + my $tag_num = substr( $tag, 0, 3 ); + # subfields are the remainder + my $subfield_letters = substr( $tag, 3 ); + + my @fields = $marc_record->field( $tag_num ); + foreach my $field (@fields) { + # If $field->indicator(1) eq 'z', it means it is a 'see from' + # field introduced because of IncludeSeeFromInSearches, so skip it + next if $field->indicator(1) eq 'z'; + + my $data = $field->as_string( $subfield_letters, $facet->{ sep } ); + + unless ( grep { /^\Q$data\E$/ } @used_datas ) { + push @used_datas, $data; + $facets_counter->{ $facet->{ idx } }->{ $data }++; + } + } + } + } +} + +=head2 _get_facets_from_zebra + + my $facets = _get_facets_from_zebra( $result_set ) + +Retrieves facets for a specified result set. It loops through the facets defined +in C4::Koha::getFacets and returns a hash with the following structure: + + { facet_idx => { + facet_value => count + }, + ... + } + +=cut + +sub _get_facets_from_zebra { + + my $rs = shift; + + # save current elementSetName + my $elementSetName = $rs->option( 'elementSetName' ); + + my $facets_loop = getFacets(); + my $facets_data = {}; + # loop through defined facets and fill the facets hashref + foreach my $facet ( @$facets_loop ) { + + my $idx = $facet->{ idx }; + my $sep = $facet->{ sep }; + my $facet_values = _get_facet_from_result_set( $idx, $rs, $sep ); + if ( $facet_values ) { + # we've actually got a result + $facets_data->{ $idx } = $facet_values; + } + } + # set elementSetName to its previous value to avoid side effects + $rs->option( elementSetName => $elementSetName ); + + return $facets_data; +} + +=head2 _get_facet_from_result_set + + my $facet_values = + C4::Search::_get_facet_from_result_set( $facet_idx, $result_set, $sep ) + +Internal function that extracts facet information for a specific index ($facet_idx) and +returns a hash containing facet values and count: + + { + $facet_value => $count , + ... + } + +Warning: this function has the side effect of changing the elementSetName for the result +set. It is a helper function for the main loop, which takes care of backing it up for +restoring. + +=cut + +sub _get_facet_from_result_set { + + my $facet_idx = shift; + my $rs = shift; + my $sep = shift; + + my $internal_sep = '<*>'; + my $facetMaxCount = C4::Context->preference('FacetMaxCount') // 20; + + return if ( ! defined $facet_idx || ! defined $rs ); + # zebra's facet element, untokenized index + my $facet_element = 'zebra::facet::' . $facet_idx . ':0:' . $facetMaxCount; + # configure zebra results for retrieving the desired facet + $rs->option( elementSetName => $facet_element ); + # get the facet record from result set + my $facet = $rs->record( 0 )->raw; + # if the facet has no restuls... + return if !defined $facet; + # TODO: benchmark DOM vs. SAX performance + my $facet_dom = XML::LibXML->load_xml( + string => ($facet) + ); + my @terms = $facet_dom->getElementsByTagName('term'); + return if ! @terms; + + my $facets = {}; + foreach my $term ( @terms ) { + my $facet_value = $term->textContent; + $facet_value =~ s/\Q$internal_sep\E/$sep/ if defined $sep; + $facets->{ $facet_value } = $term->getAttribute( 'occur' ); + } + + return $facets; +} + +=head2 _get_facets_info + + my $facets_info = C4::Search::_get_facets_info( $facets ) + +Internal function that extracts facets information and properly builds +the data structure needed to render facet labels. + +=cut + +sub _get_facets_info { + + my $facets = shift; + + my $facets_info = {}; + + for my $facet ( @$facets ) { + $facets_info->{ $facet->{ idx } }->{ label_value } = $facet->{ label }; + $facets_info->{ $facet->{ idx } }->{ expanded } = $facet->{ expanded }; + } + + return $facets_info; +} + sub pazGetRecords { my ( $koha_query, $simple_query, $sort_by_ref, $servers_ref, @@ -751,7 +919,7 @@ sub _remove_stopwords { my @stopwords_removed; # phrase and exact-qualified indexes shouldn't have stopwords removed - if ( $index !~ m/phr|ext/ ) { + if ( $index !~ m/,(phr|ext)/ ) { # remove stopwords from operand : parse all stopwords & remove them (case insensitive) # we use IsAlpha unicode definition, to deal correctly with diacritics. @@ -847,6 +1015,7 @@ sub _build_weighted_query { my $stemming = C4::Context->preference("QueryStemming") || 0; my $weight_fields = C4::Context->preference("QueryWeightFields") || 0; 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 @@ -856,6 +1025,7 @@ sub _build_weighted_query { "Title-cover,ext,r1=\"$operand\""; # exact title-cover $weighted_query .= " or ti,ext,r2=\"$operand\""; # exact title $weighted_query .= " or Title-cover,phr,r3=\"$operand\""; # phrase title + $weighted_query .= " or ti,wrdl,r4=\"$operand\""; # words in title #$weighted_query .= " or any,ext,r4=$operand"; # exact any #$weighted_query .=" or kw,wrdl,r5=\"$operand\""; # word list any $weighted_query .= " or wrdl,fuzzy,r8=\"$operand\"" @@ -956,11 +1126,14 @@ sub getIndexes{ 'Corporate-name-heading', 'Corporate-name-see', 'Corporate-name-seealso', + 'Country-publication', 'ctype', + 'curriculum', 'date-entered-on-file', 'Date-of-acquisition', 'Date-of-publication', 'Dewey-classification', + 'Dissertation-information', 'EAN', 'extent', 'fic', @@ -976,6 +1149,8 @@ sub getIndexes{ 'Host-item', 'id-other', 'Illustration-code', + 'Index-term-genre', + 'Index-term-uncontrolled', 'ISBN', 'isbn', 'ISSN', @@ -985,11 +1160,15 @@ sub getIndexes{ 'Koha-Auth-Number', 'l-format', 'language', + 'language-original', 'lc-card', 'LC-card-number', 'lcn', + 'lex', 'llength', 'ln', + 'ln-audio', + 'ln-subtitle', 'Local-classification', 'Local-number', 'Match-heading', @@ -1041,7 +1220,6 @@ sub getIndexes{ 'su-to', 'su-ut', 'ut', - 'UPC', 'Term-genre-form', 'Term-genre-form-heading', 'Term-genre-form-see', @@ -1050,7 +1228,6 @@ sub getIndexes{ 'Title', 'Title-cover', 'Title-series', - 'Title-host', 'Title-uniform', 'Title-uniform-heading', 'Title-uniform-see', @@ -1085,6 +1262,7 @@ sub getIndexes{ 'mc-itype', 'mc-loc', 'notforloan', + 'Number-local-acquisition', 'onloan', 'price', 'renewals', @@ -1187,7 +1365,10 @@ sub parseQuery { next unless $operands[$ii]; $query .= $operators[ $ii - 1 ] eq 'or' ? ' || ' : ' && ' if ($query); - if ( $indexes[$ii] =~ m/su-/ ) { + if ( $operands[$ii] =~ /^[^"]\W*[-|_\w]*:\w.*[^"]$/ ) { + $query .= $operands[$ii]; + } + elsif ( $indexes[$ii] =~ m/su-/ ) { $query .= $indexes[$ii] . '(' . $operands[$ii] . ')'; } else { @@ -1280,7 +1461,7 @@ sub buildQuery { my $cclq = 0; my $cclindexes = getIndexes(); - if ( $query !~ /\s*ccl=/ ) { + if ( $query !~ /\s*(ccl=|pqf=|cql=)/ ) { while ( !$cclq && $query =~ /(?:^|\W)([\w-]+)(,[\w-]+)*[:=]/g ) { my $dx = lc($1); $cclq = grep { lc($_) eq $dx } @$cclindexes; @@ -1357,17 +1538,28 @@ sub buildQuery { my $index = $indexes[$i]; # Add index-specific attributes + + #Afaik, this 'yr' condition will only ever be met in the staff client 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. + # Date of Publication - if ( $index eq 'yr' ) { - $index .= ",st-numeric"; - $indexes_set++; + if ( $index =~ /yr/ ) { + #weight_fields/relevance search causes errors with date ranges + #In the case of YYYY-, it will only return records with a 'yr' of YYYY (not the range) + #In the case of YYYY-YYYY, it will return no results $stemming = $auto_truncation = $weight_fields = $fuzzy_enabled = $remove_stopwords = 0; } # Date of Acquisition - elsif ( $index eq 'acqdate' ) { - $index .= ",st-date-normalized"; - $indexes_set++; + elsif ( $index =~ /acqdate/ ) { + #stemming and auto_truncation would have zero impact since it already is YYYY-MM-DD format + #Weight_fields probably SHOULD be turned OFF, otherwise you'll get records floating to the + #top of the results just because they have lots of item records matching that date. + #Fuzzy actually only applies during _build_weighted_query, and is reset there anyway, so + #irrelevant here + #remove_stopwords doesn't function anymore so is irrelevant $stemming = $auto_truncation = $weight_fields = $fuzzy_enabled = $remove_stopwords = 0; } # ISBN,ISSN,Standard Number, don't need special treatment @@ -1386,7 +1578,7 @@ sub buildQuery { # Set default structure attribute (word list) my $struct_attr = q{}; - unless ( $indexes_set || !$index || $index =~ /(st-|phr|ext|wrdl|nb|ns)/ ) { + unless ( $indexes_set || !$index || $index =~ /,(st-|phr|ext|wrdl)/ || $index =~ /^(nb|ns)$/ ) { $struct_attr = ",wrdl"; } @@ -1404,7 +1596,7 @@ sub buildQuery { } if ($auto_truncation){ - unless ( $index =~ /(st-|phr|ext)/ ) { + unless ( $index =~ /,(st-|phr|ext)/ ) { #FIXME only valid with LTR scripts $operand=join(" ",map{ (index($_,"*")>0?"$_":"$_*") @@ -1472,43 +1664,19 @@ sub buildQuery { warn "FIELD WEIGHTED OPERAND: >$weighted_operand<" if $DEBUG; - # If there's a previous operand, we need to add an operator - if ($previous_operand) { - - # User-specified operator - if ( $operators[ $i - 1 ] ) { - $query .= " $operators[$i-1] "; - $query .= " $index_plus " unless $indexes_set; - $query .= " $operand"; - $query_cgi .= "&op=".uri_escape($operators[$i-1]); - $query_cgi .= "&idx=".uri_escape($index) if $index; - $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i]; - $query_desc .= - " $operators[$i-1] $index_plus $operands[$i]"; - } - - # Default operator is and - else { - $query .= " and "; - $query .= "$index_plus " unless $indexes_set; - $query .= "$operand"; - $query_cgi .= "&op=and&idx=".uri_escape($index) if $index; - $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i]; - $query_desc .= " and $index_plus $operands[$i]"; - } - } + ($query,$query_cgi,$query_desc,$previous_operand) = _build_initial_query({ + query => $query, + query_cgi => $query_cgi, + query_desc => $query_desc, + operator => ($operators[ $i - 1 ]) ? $operators[ $i - 1 ] : '', + parsed_operand => $operand, + original_operand => ($operands[$i]) ? $operands[$i] : '', + index => $index, + index_plus => $index_plus, + indexes_set => $indexes_set, + previous_operand => $previous_operand, + }); - # There isn't a pervious operand, don't need an operator - else { - - # Field-weighted queries already have indexes set - $query .= " $index_plus " unless $indexes_set; - $query .= $operand; - $query_desc .= " $index_plus $operands[$i]"; - $query_cgi .= "&idx=".uri_escape($index) if $index; - $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i]; - $previous_operand = 1; - } } #/if $operands } # /for } @@ -1543,7 +1711,7 @@ sub buildQuery { $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=$this_limit"; + $limit_cgi .= "&limit=" . uri_escape($this_limit); $limit_desc .= " $this_limit"; } @@ -1551,7 +1719,7 @@ sub buildQuery { else { $limit .= " and " if $limit || $query; $limit .= "$this_limit"; - $limit_cgi .= "&limit=$this_limit"; + $limit_cgi .= "&limit=" . uri_escape($this_limit); if ($this_limit =~ /^branch:(.+)/) { my $branchcode = $1; my $branchname = GetBranchName($branchcode); @@ -1578,9 +1746,13 @@ sub buildQuery { # 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 # $query =~ s/:/=/g; + #NOTE: We use several several different regexps here as you can't have variable length lookback assertions $query =~ s/(?<=(ti|au|pb|su|an|kw|mc|nb|ns)):/=/g; $query =~ s/(?<=(wrdl)):/=/g; $query =~ s/(?<=(trn|phr)):/=/g; + $query =~ s/(?<=(st-numeric)):/=/g; + $query =~ s/(?<=(st-year)):/=/g; + $query =~ s/(?<=(st-date-normalized)):/=/g; $limit =~ s/:/=/g; for ( $query, $query_desc, $limit, $limit_desc ) { s/ +/ /g; # remove extra spaces @@ -1613,6 +1785,42 @@ sub buildQuery { ); } +=head2 _build_initial_query + + ($query, $query_cgi, $query_desc, $previous_operand) = _build_initial_query($initial_query_params); + + Build a section of the initial query containing indexes, operators, and operands. + +=cut + +sub _build_initial_query { + my ($params) = @_; + + 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 '; + } + + #NOTE: indexes_set is typically set when doing truncation or field weighting + my $operand = ($params->{indexes_set}) ? $params->{parsed_operand} : $params->{index_plus}.$params->{parsed_operand}; + + #e.g. "kw,wrdl:test" + #e.g. " and kw,wrdl:test" + $params->{query} .= $operator . $operand; + + $params->{query_cgi} .= "&op=".uri_escape($operator) if $operator; + $params->{query_cgi} .= "&idx=".uri_escape($params->{index}) if $params->{index}; + $params->{query_cgi} .= "&q=".uri_escape($params->{original_operand}) if $params->{original_operand}; + + #e.g. " and kw,wrdl: test" + $params->{query_desc} .= $operator . $params->{index_plus} . " " . $params->{original_operand}; + + $params->{previous_operand} = 1 unless $params->{previous_operand}; #If there is no previous operand, mark this as one + + return ($params->{query}, $params->{query_cgi}, $params->{query_desc}, $params->{previous_operand}); +} + =head2 searchResults my @search_results = searchResults($search_context, $searchdesc, $hits, @@ -1680,7 +1888,9 @@ sub searchResults { while ( ( my $column ) = $sth2->fetchrow ) { my ( $tagfield, $tagsubfield ) = &GetMarcFromKohaField( "items." . $column, "" ); - $subfieldstosearch{$column} = $tagsubfield; + if ( defined $tagsubfield ) { + $subfieldstosearch{$column} = $tagsubfield; + } } # handle which records to actually retrieve @@ -1692,13 +1902,30 @@ sub searchResults { $times = $hits; # FIXME: if $hits is undefined, why do we want to equal it? } - my $marcflavour = C4::Context->preference("marcflavour"); + my $marcflavour = C4::Context->preference("marcflavour"); # We get the biblionumber position in MARC my ($bibliotag,$bibliosubf)=GetMarcFromKohaField('biblio.biblionumber',''); # loop through all of the records we've retrieved for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) { - my $marcrecord = MARC::File::USMARC::decode( $marcresults->[$i] ); + + my $marcrecord; + if ($scan) { + # For Scan searches we built USMARC data + $marcrecord = MARC::Record->new_from_usmarc( $marcresults->[$i]); + } else { + # Normal search, render from Zebra's output + $marcrecord = new_record_from_zebra( + 'biblioserver', + $marcresults->[$i] + ); + + if ( ! defined $marcrecord ) { + warn "ERROR DECODING RECORD - $@: " . $marcresults->[$i]; + next; + } + } + my $fw = $scan ? undef : $bibliotag < 10 @@ -1823,6 +2050,7 @@ sub searchResults { my $item_in_transit_count = 0; my $can_place_holds = 0; my $item_onhold_count = 0; + my $notforloan_count = 0; my $items_count = scalar(@fields); my $maxitems_pref = C4::Context->preference('maxItemsinSearchResults'); my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1; @@ -1854,8 +2082,8 @@ sub searchResults { } } - my $hbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'homebranch' : 'holdingbranch'; - my $otherbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'holdingbranch' : 'homebranch'; + my $hbranch = C4::Context->preference('StaffSearchResultsDisplayBranch'); + my $otherbranch = $hbranch eq 'homebranch' ? 'holdingbranch' : 'homebranch'; # set item's branch name, use HomeOrHoldingBranch syspref first, fall back to the other one if ($item->{$hbranch}) { @@ -1893,6 +2121,8 @@ sub searchResults { # item is on order if ( $item->{notforloan} < 0 ) { $ordered_count++; + } elsif ( $item->{notforloan} > 0 ) { + $notforloan_count++; } # is item in transit? @@ -1999,13 +2229,12 @@ sub searchResults { } # XSLT processing of some stuff - use C4::Charset; - SetUTF8Flag($marcrecord); + SetUTF8Flag($marcrecord); warn $marcrecord->as_formatted if $DEBUG; - my $interface = $search_context eq 'opac' ? 'OPAC' : ''; - if (!$scan && C4::Context->preference($interface . "XSLTResultsDisplay")) { + my $interface = $search_context eq 'opac' ? 'OPAC' : ''; + if (!$scan && C4::Context->preference($interface . "XSLTResultsDisplay")) { $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, $interface."XSLTResultsDisplay", 1, \@hiddenitems); - # the last parameter tells Koha to clean up the problematic ampersand entities that Zebra outputs + # the last parameter tells Koha to clean up the problematic ampersand entities that Zebra outputs } # if biblio level itypes are used and itemtype is notforloan, it can't be reserved either @@ -2032,6 +2261,7 @@ sub searchResults { $oldbiblio->{intransitcount} = $item_in_transit_count; $oldbiblio->{onholdcount} = $item_onhold_count; $oldbiblio->{orderedcount} = $ordered_count; + $oldbiblio->{notforloancount} = $notforloan_count; if (C4::Context->preference("AlternateHoldingsField") && $items_count == 0) { my $fieldspec = C4::Context->preference("AlternateHoldingsField"); @@ -2189,28 +2419,6 @@ sub enabled_staff_search_views ); } -sub AddSearchHistory{ - my ($borrowernumber,$session,$query_desc,$query_cgi, $total)=@_; - my $dbh = C4::Context->dbh; - - # Add the request the user just made - my $sql = "INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time) VALUES(?, ?, ?, ?, ?, NOW())"; - my $sth = $dbh->prepare($sql); - $sth->execute($borrowernumber, $session, $query_desc, $query_cgi, $total); - return $dbh->last_insert_id(undef, 'search_history', undef,undef,undef); -} - -sub GetSearchHistory{ - my ($borrowernumber,$session)=@_; - my $dbh = C4::Context->dbh; - - # Add the request the user just made - my $query = "SELECT FROM search_history WHERE (userid=? OR sessionid=?)"; - my $sth = $dbh->prepare($query); - $sth->execute($borrowernumber, $session); - return $sth->fetchall_hashref({}); -} - sub PurgeSearchHistory{ my ($pSearchhistory)=@_; my $dbh = C4::Context->dbh; @@ -2224,7 +2432,7 @@ $arrayref = z3950_search_args($matchpoints) This function returns an array reference that contains the search parameters to be passed to the Z39.50 search script (z3950_search.pl). The array elements -are hash refs whose keys are name, value and encvalue, and whose values are the +are hash refs whose keys are name and value, and whose values are the name of a search parameter, the value of that search parameter and the URL encoded value of that parameter. @@ -2235,7 +2443,7 @@ data is in a hash reference in $matchpoints, as returned by Biblio::GetBiblioDat If $matchpoints is a scalar, it is assumed to be an unnamed query descriptor, e.g. a general purpose search argument. In this case, the returned array contains only -entry: the key is 'title' and the value and encvalue are derived from $matchpoints. +entry: the key is 'title' and the value is derived from $matchpoints. If a search parameter value is undefined or empty, it is not included in the returned array. @@ -2263,11 +2471,18 @@ $template->param ( MYLOOP => C4::Search::z3950_search_args($searchscalar) ) sub z3950_search_args { my $bibrec = shift; - my $isbn = Business::ISBN->new($bibrec); + + my $isbn_string = ref( $bibrec ) ? $bibrec->{title} : $bibrec; + my $isbn = Business::ISBN->new( $isbn_string ); if (defined $isbn && $isbn->is_valid) { - $bibrec = { isbn => $bibrec } if !ref $bibrec; + if ( ref($bibrec) ) { + $bibrec->{isbn} = $isbn_string; + $bibrec->{title} = undef; + } else { + $bibrec = { isbn => $isbn_string }; + } } else { $bibrec = { title => $bibrec } if !ref $bibrec; @@ -2275,8 +2490,8 @@ sub z3950_search_args { my $array = []; for my $field (qw/ lccn isbn issn title author dewey subject /) { - my $encvalue = URI::Escape::uri_escape_utf8($bibrec->{$field}); - push @$array, { name=>$field, value=>$bibrec->{$field}, encvalue=>$encvalue } if defined $bibrec->{$field}; + push @$array, { name => $field, value => $bibrec->{$field} } + if defined $bibrec->{$field}; } return $array; } @@ -2359,6 +2574,43 @@ sub _ZOOM_event_loop { } } +=head2 new_record_from_zebra + +Given raw data from a Zebra result set, return a MARC::Record object + +This helper function is needed to take into account all the involved +system preferences and configuration variables to properly create the +MARC::Record object. + +If we are using GRS-1, then the raw data we get from Zebra should be USMARC +data. If we are using DOM, then it has to be MARCXML. + +=cut + +sub new_record_from_zebra { + + my $server = shift; + my $raw_data = shift; + # Set the default indexing modes + my $index_mode = ( $server eq 'biblioserver' ) + ? C4::Context->config('zebra_bib_index_mode') // 'dom' + : C4::Context->config('zebra_auth_index_mode') // 'dom'; + + my $marc_record = eval { + if ( $index_mode eq 'dom' ) { + MARC::Record->new_from_xml( $raw_data, 'UTF-8' ); + } else { + MARC::Record->new_from_usmarc( $raw_data ); + } + }; + + if ($@) { + return; + } else { + return $marc_record; + } + +} END { } # module clean-up code here (global destructor)