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;
use URI::Escape;
use Business::ISBN;
-
+use MARC::Record;
+use MARC::Field;
+use utf8;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG);
# set the version for version checking
&searchResults
&getRecords
&buildQuery
- &NZgetRecords
&AddSearchHistory
&GetDistinctValues
&enabled_staff_search_views
- &SimpleSearch
+ &PurgeSearchHistory
);
# make all your functions, whether exported or not;
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 = '&&';
+ $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
+ } else {
+ $titleindex = 'ti,ext';
+ $authorindex = 'au,ext';
+ $op = 'and';
+ }
+
$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 = "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;
# remove valid operators
$result->{author} =~ s/(and|or|not)//g;
- $query .= " and au,ext=$result->{author}";
+ $query .= " $op $authorindex:\"$result->{author}\"";
}
}
sub SimpleSearch {
my ( $query, $offset, $max_results, $servers ) = @_;
- if ( C4::Context->preference('NoZebra') ) {
- my $result = NZorder( NZanalyse($query) )->{'biblioserver'};
- my $search_result =
- ( $result->{hits}
- && $result->{hits} > 0 ? $result->{'RECORDS'} : [] );
- return ( undef, $search_result, scalar($result->{hits}) );
+ return ( 'No query entered', undef, undef ) unless $query;
+ # FIXME hardcoded value. See catalog/search.pl & opac-search.pl too.
+ my @servers = defined ( $servers ) ? @$servers : ( 'biblioserver' );
+ my @zoom_queries;
+ my @tmpresults;
+ my @zconns;
+ 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');
}
- else {
- return ( 'No query entered', undef, undef ) unless $query;
- # FIXME hardcoded value. See catalog/search.pl & opac-search.pl too.
- my @servers = defined ( $servers ) ? @$servers : ( 'biblioserver' );
- my @zoom_queries;
- my @tmpresults;
- my @zconns;
- my $results = [];
- my $total_hits = 0;
-
- # Initialize & Search Zebra
- for ( my $i = 0 ; $i < @servers ; $i++ ) {
- eval {
- $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
+
+ # Initialize & Search Zebra
+ for ( my $i = 0 ; $i < @servers ; $i++ ) {
+ eval {
+ $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
+ 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 {
+ $query =~ s/:/=/g;
$zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]);
- $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] );
-
- # error handling
- my $error =
- $zconns[$i]->errmsg() . " ("
- . $zconns[$i]->errcode() . ") "
- . $zconns[$i]->addinfo() . " "
- . $zconns[$i]->diagset();
-
- return ( $error, undef, undef ) if $zconns[$i]->errcode();
- };
- if ($@) {
-
- # caught a ZOOM::Exception
- my $error =
- $@->message() . " ("
- . $@->code() . ") "
- . $@->addinfo() . " "
- . $@->diagset();
- warn $error;
- return ( $error, undef, undef );
}
+ $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] );
+
+ # error handling
+ my $error =
+ $zconns[$i]->errmsg() . " ("
+ . $zconns[$i]->errcode() . ") "
+ . $zconns[$i]->addinfo() . " "
+ . $zconns[$i]->diagset();
+
+ return ( $error, undef, undef ) if $zconns[$i]->errcode();
+ };
+ if ($@) {
+
+ # caught a ZOOM::Exception
+ my $error =
+ $@->message() . " ("
+ . $@->code() . ") "
+ . $@->addinfo() . " "
+ . $@->diagset();
+ warn $error." for query: $query";
+ 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;
- 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;
- }
+ }
- for my $j ( $first_record..$last_record ) {
- my $record = $tmpresults[ $i - 1 ]->record( $j-1 )->raw(); # 0 indexed
- push @{$results}, $record;
- }
+ _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;
}
- }
- foreach my $result (@tmpresults) {
- $result->destroy();
- }
- foreach my $zoom_query (@zoom_queries) {
- $zoom_query->destroy();
+ for my $j ( $first_record .. $last_record ) {
+ my $record =
+ $tmpresults[ $i - 1 ]->record( $j - 1 )->raw()
+ ; # 0 indexed
+ push @{$results}, $record;
+ }
}
+ );
- return ( undef, $results, $total_hits );
+ foreach my $zoom_query (@zoom_queries) {
+ $zoom_query->destroy();
}
+
+ return ( undef, $results, $total_hits );
}
=head2 getRecords
( undef, $results_hashref, \@facets_loop ) = getRecords (
$koha_query, $simple_query, $sort_by_ref, $servers_ref,
- $results_per_page, $offset, $expanded_facet, $branches,
+ $results_per_page, $offset, $expanded_facet, $branches,$itemtypes,
$query_type, $scan
);
my (
$koha_query, $simple_query, $sort_by_ref, $servers_ref,
$results_per_page, $offset, $expanded_facet, $branches,
- $query_type, $scan
+ $itemtypes, $query_type, $scan, $opac
) = @_;
my @servers = @$servers_ref;
} # 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
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
}
$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 . ' ([^\n]+)';
- 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} }
+ )
{
-
- # Sanitize the link value ), ( 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;
+ $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;
# 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 ( $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" )
+ {
+ $facet_label_value =
+ $itemtypes->{$one_facet}
+ ->{'description'};
+ }
+ }
+
+ # 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,
- };
}
- }
- # 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 );
}
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.
sub _build_stemmed_operand {
my ($operand,$lang) = @_;
require Lingua::Stem::Snowball ;
- my $stemmed_operand;
+ my $stemmed_operand=q{};
# If operand contains a digit, it is almost certainly an identifier, and should
# not be stemmed. This is particularly relevant for ISBNs and ISSNs, which
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
$weighted_query .= " $index,ext,r1=\"$operand\""; # exact index
#$weighted_query .= " or (title-sort-az=0 or $index,startswithnt,st-word,r3=$operand #)";
$weighted_query .= " or $index,phr,r3=\"$operand\""; # phrase index
- $weighted_query .=
- " or $index,rt,wrdl,r3=\"$operand\""; # word list index
+ $weighted_query .= " or $index,wrdl,r6=\"$operand\""; # word list index
+ $weighted_query .= " or $index,wrdl,fuzzy,r8=\"$operand\""
+ if $fuzzy_enabled; # add fuzzy, word list
+ $weighted_query .= " or $index,wrdl,rt,r9=\"$stemmed_operand\""
+ if ( $stemming and $stemmed_operand ); # add stemming, right truncation
}
$weighted_query .= "))"; # close rank specification
'Corporate-name-heading',
'Corporate-name-see',
'Corporate-name-seealso',
+ 'Country-publication',
'ctype',
'date-entered-on-file',
'Date-of-acquisition',
'Koha-Auth-Number',
'l-format',
'language',
+ 'language-original',
'lc-card',
'LC-card-number',
'lcn',
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 ) =
+ buildQuery ( $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,
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 : ();
my @indexes = $indexes ? @$indexes : ();
my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
my $remove_stopwords = C4::Context->preference("QueryRemoveStopwords") || 0;
- # no stemming/weight/fuzzy in NoZebra
- if ( C4::Context->preference("NoZebra") ) {
- $stemming = 0;
- $weight_fields = 0;
- $fuzzy_enabled = 0;
- $auto_truncation = 0;
- }
-
my $query = $operands[0];
my $simple_query = $operands[0];
# initialize the variables we're passing back
my $query_cgi;
- my $query_desc;
my $query_type;
my $limit;
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;
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
- if (@limits) {
+ @limits = grep {!/^$/} @limits;
+ if ( @limits ) {
$q .= ' and '.join(' and ', @limits);
}
- return ( undef, $q, $q, "q=ccl=$q", $q, '', '', '', '', 'ccl' );
+ return ( undef, $q, $q, "q=ccl=".uri_escape($q), $q, '', '', '', '', 'ccl' );
}
if ( $query =~ /^cql=/ ) {
- return ( undef, $', $', "q=cql=$'", $', '', '', '', '', 'cql' );
+ return ( undef, $', $', "q=cql=".uri_escape($'), $', '', '', '', '', 'cql' );
}
if ( $query =~ /^pqf=/ ) {
- return ( undef, $', $', "q=pqf=$'", $', '', '', '', '', 'pqf' );
+ if ($query_desc) {
+ $query_cgi = "q=".uri_escape($query_desc);
+ } else {
+ $query_desc = $';
+ $query_cgi = "q=pqf=".uri_escape($');
+ }
+ return ( undef, $', $', $query_cgi, $query_desc, '', '', '', '', 'pqf' );
}
# pass nested queries directly
# 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";
}
}
if ($auto_truncation){
- unless ( $index =~ /(st-|phr|ext)/ ) {
+ unless ( $index =~ /,(st-|phr|ext)/ ) {
#FIXME only valid with LTR scripts
$operand=join(" ",map{
(index($_,"*")>0?"$_":"$_*")
$query .= " $operators[$i-1] ";
$query .= " $index_plus " unless $indexes_set;
$query .= " $operand";
- $query_cgi .= "&op=$operators[$i-1]";
- $query_cgi .= "&idx=$index" if $index;
- $query_cgi .= "&q=$operands[$i]" if $operands[$i];
+ $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]";
}
$query .= " and ";
$query .= "$index_plus " unless $indexes_set;
$query .= "$operand";
- $query_cgi .= "&op=and&idx=$index" if $index;
- $query_cgi .= "&q=$operands[$i]" if $operands[$i];
+ $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 .= " $index_plus " unless $indexes_set;
$query .= $operand;
$query_desc .= " $index_plus $operands[$i]";
- $query_cgi .= "&idx=$index" if $index;
- $query_cgi .= "&q=$operands[$i]" if $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
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)
my @search_results = searchResults($search_context, $searchdesc, $hits,
$results_per_page, $offset, $scan,
- @marcresults, $hidelostitems);
+ @marcresults);
Format results in a form suitable for passing to the template
}
#search item field code
- my $sth =
- $dbh->prepare(
-"SELECT tagfield FROM marc_subfield_structure WHERE kohafield LIKE 'items.itemnumber'"
- );
- $sth->execute;
- my ($itemtag) = $sth->fetchrow;
+ my ($itemtag, undef) = &GetMarcFromKohaField( "items.itemnumber", "" );
## find column names of items related to MARC
my $sth2 = $dbh->prepare("SHOW COLUMNS FROM items");
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
# 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 = eval { MARC::File::USMARC::decode( $marcresults->[$i] ); };
+ if ( $@ ) {
+ warn "ERROR DECODING RECORD - $@: " . $marcresults->[$i];
+ next;
+ }
+
my $fw = $scan
? undef
: $bibliotag < 10
my $onloan_count = 0;
my $longoverdue_count = 0;
my $other_count = 0;
- my $wthdrawn_count = 0;
+ my $withdrawn_count = 0;
my $itemlost_count = 0;
my $hideatopac_count = 0;
my $itembinding_count = 0;
my $items_count = scalar(@fields);
my $maxitems_pref = C4::Context->preference('maxItemsinSearchResults');
my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1;
+ my @hiddenitems; # hidden itemnumbers based on OpacHiddenItems syspref
# loop through every item
- my @hiddenitems;
foreach my $field (@fields) {
my $item;
}
$item->{description} = $itemtypes{ $item->{itype} }{description};
- # Hidden items
+ # 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($item);
- $item->{'hideatopac'} = @hi;
- push @hiddenitems, @hi;
+ if (scalar @hi) {
+ push @hiddenitems, @hi;
+ $hideatopac_count++;
+ next;
+ }
}
my $hbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'homebranch' : 'holdingbranch';
else {
# item is on order
- if ( $item->{notforloan} == -1 ) {
+ if ( $item->{notforloan} < 0 ) {
$ordered_count++;
}
my ($transfertfrom, $transfertto);
# is item on the reserve shelf?
- my $reservestatus = '';
- my $reserveitem;
+ my $reservestatus = '';
- unless ($item->{wthdrawn}
+ unless ($item->{withdrawn}
|| $item->{itemlost}
|| $item->{damaged}
|| $item->{notforloan}
# 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}
+ if ( $item->{withdrawn}
|| $item->{itemlost}
|| $item->{damaged}
- || $item->{notforloan} > 0
- || $item->{hideatopac}
- || $reservestatus eq 'Waiting'
+ || $item->{notforloan}
+ || $reservestatus eq 'Waiting'
|| ($transfertwhen ne ''))
{
- $wthdrawn_count++ if $item->{wthdrawn};
+ $withdrawn_count++ if $item->{withdrawn};
$itemlost_count++ if $item->{itemlost};
$itemdamaged_count++ if $item->{damaged};
- $hideatopac_count++ if $item->{hideatopac};
$item_in_transit_count++ if $transfertwhen ne '';
- $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;
- }
-
+ $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 )
+ );
+
$other_count++;
my $key = $prefix . $item->{status};
- foreach (qw(wthdrawn itemlost damaged branchname itemcallnumber hideatopac)) {
+ foreach (qw(withdrawn itemlost damaged branchname itemcallnumber)) {
$other_items->{$key}->{$_} = $item->{$_};
}
$other_items->{$key}->{intransit} = ( $transfertwhen ne '' ) ? 1 : 0;
$can_place_holds = 1;
$available_count++;
$available_items->{$prefix}->{count}++ if $item->{$hbranch};
- foreach (qw(branchname itemcallnumber hideatopac description)) {
+ foreach (qw(branchname itemcallnumber description)) {
$available_items->{$prefix}->{$_} = $item->{$_};
}
$available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} };
}
}
} # notforloan, item level and biblioitem level
- if ($items_count > 0) {
- next if $is_opac && $hideatopac_count >= $items_count;
- next if $hidelostitems && $itemlost_count >= $items_count;
- }
+
+ # if all items are hidden, do not show the record
+ if ($items_count > 0 && $hideatopac_count == $items_count) {
+ next;
+ }
+
my ( $availableitemscount, $onloanitemscount, $otheritemscount );
for my $key ( sort keys %$onloan_items ) {
(++$onloanitemscount > $maxitems) and last;
# XSLT processing of some stuff
use C4::Charset;
SetUTF8Flag($marcrecord);
- $debug && warn $marcrecord->as_formatted;
+ warn $marcrecord->as_formatted if $DEBUG;
my $interface = $search_context eq 'opac' ? 'OPAC' : '';
if (!$scan && C4::Context->preference($interface . "XSLTResultsDisplay")) {
$oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, $interface."XSLTResultsDisplay", 1, \@hiddenitems);
$oldbiblio->{onloanplural} = 1 if $onloan_count > 1;
$oldbiblio->{othercount} = $other_count;
$oldbiblio->{otherplural} = 1 if $other_count > 1;
- $oldbiblio->{wthdrawncount} = $wthdrawn_count;
+ $oldbiblio->{withdrawncount} = $withdrawn_count;
$oldbiblio->{itemlostcount} = $itemlost_count;
$oldbiblio->{damagedcount} = $itemdamaged_count;
$oldbiblio->{intransitcount} = $item_in_transit_count;
$oldbiblio->{onholdcount} = $item_onhold_count;
$oldbiblio->{orderedcount} = $ordered_count;
- # deleting - in isbn to enable amazon content
- $oldbiblio->{isbn} =~ s/-//g;
if (C4::Context->preference("AlternateHoldingsField") && $items_count == 0) {
my $fieldspec = C4::Context->preference("AlternateHoldingsField");
$qdataacquisitions->finish;
return \@loopacquisitions;
}
-#----------------------------------------------------------------------
-#
-# Non-Zebra GetRecords#
-#----------------------------------------------------------------------
-
-=head2 NZgetRecords
-
- NZgetRecords has the same API as zera getRecords, even if some parameters are not managed
-
-=cut
-
-sub NZgetRecords {
- my (
- $query, $simple_query, $sort_by_ref, $servers_ref,
- $results_per_page, $offset, $expanded_facet, $branches,
- $query_type, $scan
- ) = @_;
- warn "query =$query" if $DEBUG;
- my $result = NZanalyse($query);
- warn "results =$result" if $DEBUG;
- return ( undef,
- NZorder( $result, @$sort_by_ref[0], $results_per_page, $offset ),
- undef );
-}
-
-=head2 NZanalyse
-
- NZanalyse : get a CQL string as parameter, and returns a list of biblionumber;title,biblionumber;title,...
- the list is built from an inverted index in the nozebra SQL table
- note that title is here only for convenience : the sorting will be very fast when requested on title
- if the sorting is requested on something else, we will have to reread all results, and that may be longer.
-
-=cut
-
-sub NZanalyse {
- my ( $string, $server ) = @_;
-# warn "---------" if $DEBUG;
- warn " NZanalyse" if $DEBUG;
-# warn "---------" if $DEBUG;
-
- # $server contains biblioserver or authorities, depending on what we search on.
- #warn "querying : $string on $server";
- $server = 'biblioserver' unless $server;
-
-# if we have a ", replace the content to discard temporarily any and/or/not inside
- my $commacontent;
- if ( $string =~ /"/ ) {
- $string =~ s/"(.*?)"/__X__/;
- $commacontent = $1;
- warn "commacontent : $commacontent" if $DEBUG;
- }
-
-# split the query string in 3 parts : X AND Y means : $left="X", $operand="AND" and $right="Y"
-# then, call again NZanalyse with $left and $right
-# (recursive until we find a leaf (=> something without and/or/not)
-# delete repeated operator... Would then go in infinite loop
- while ( $string =~ s/( and| or| not| AND| OR| NOT)\1/$1/g ) {
- }
-
- #process parenthesis before.
- if ( $string =~ /^\s*\((.*)\)(( and | or | not | AND | OR | NOT )(.*))?/ ) {
- my $left = $1;
- my $right = $4;
- my $operator = lc($3); # FIXME: and/or/not are operators, not operands
- warn
-"dealing w/parenthesis before recursive sub call. left :$left operator:$operator right:$right"
- if $DEBUG;
- my $leftresult = NZanalyse( $left, $server );
- if ($operator) {
- my $rightresult = NZanalyse( $right, $server );
-
- # OK, we have the results for right and left part of the query
- # depending of operand, intersect, union or exclude both lists
- # to get a result list
- if ( $operator eq ' and ' ) {
- return NZoperatorAND($leftresult,$rightresult);
- }
- elsif ( $operator eq ' or ' ) {
-
- # just merge the 2 strings
- return $leftresult . $rightresult;
- }
- elsif ( $operator eq ' not ' ) {
- return NZoperatorNOT($leftresult,$rightresult);
- }
- }
- else {
-# this error is impossible, because of the regexp that isolate the operand, but just in case...
- return $leftresult;
- }
- }
- warn "string :" . $string if $DEBUG;
- my $left = "";
- my $right = "";
- my $operator = "";
- if ($string =~ /(.*?)( and | or | not | AND | OR | NOT )(.*)/) {
- $left = $1;
- $right = $3;
- $operator = lc($2); # FIXME: and/or/not are operators, not operands
- }
- warn "no parenthesis. left : $left operator: $operator right: $right"
- if $DEBUG;
-
- # it's not a leaf, we have a and/or/not
- if ($operator) {
-
- # reintroduce comma content if needed
- $right =~ s/__X__/"$commacontent"/ if $commacontent;
- $left =~ s/__X__/"$commacontent"/ if $commacontent;
- warn "node : $left / $operator / $right\n" if $DEBUG;
- my $leftresult = NZanalyse( $left, $server );
- my $rightresult = NZanalyse( $right, $server );
- warn " leftresult : $leftresult" if $DEBUG;
- warn " rightresult : $rightresult" if $DEBUG;
- # OK, we have the results for right and left part of the query
- # depending of operand, intersect, union or exclude both lists
- # to get a result list
- if ( $operator eq ' and ' ) {
- return NZoperatorAND($leftresult,$rightresult);
- }
- elsif ( $operator eq ' or ' ) {
-
- # just merge the 2 strings
- return $leftresult . $rightresult;
- }
- elsif ( $operator eq ' not ' ) {
- return NZoperatorNOT($leftresult,$rightresult);
- }
- else {
-
-# this error is impossible, because of the regexp that isolate the operand, but just in case...
- die "error : operand unknown : $operator for $string";
- }
-
- # it's a leaf, do the real SQL query and return the result
- }
- else {
- $string =~ s/__X__/"$commacontent"/ if $commacontent;
- $string =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|&|\+|\*|\// /g;
- #remove trailing blank at the beginning
- $string =~ s/^ //g;
- warn "leaf:$string" if $DEBUG;
-
- # parse the string in in operator/operand/value again
- my $left = "";
- my $operator = "";
- my $right = "";
- if ($string =~ /(.*)(>=|<=)(.*)/) {
- $left = $1;
- $operator = $2;
- $right = $3;
- } else {
- $left = $string;
- }
-# warn "handling leaf... left:$left operator:$operator right:$right"
-# if $DEBUG;
- unless ($operator) {
- if ($string =~ /(.*)(>|<|=)(.*)/) {
- $left = $1;
- $operator = $2;
- $right = $3;
- warn
- "handling unless (operator)... left:$left operator:$operator right:$right"
- if $DEBUG;
- } else {
- $left = $string;
- }
- }
- my $results;
-
-# strip adv, zebra keywords, currently not handled in nozebra: wrdl, ext, phr...
- $left =~ s/ .*$//;
-
- # automatic replace for short operators
- $left = 'title' if $left =~ '^ti$';
- $left = 'author' if $left =~ '^au$';
- $left = 'publisher' if $left =~ '^pb$';
- $left = 'subject' if $left =~ '^su$';
- $left = 'koha-Auth-Number' if $left =~ '^an$';
- $left = 'keyword' if $left =~ '^kw$';
- $left = 'itemtype' if $left =~ '^mc$'; # Fix for Bug 2599 - Search limits not working for NoZebra
- warn "handling leaf... left:$left operator:$operator right:$right" if $DEBUG;
- my $dbh = C4::Context->dbh;
- if ( $operator && $left ne 'keyword' ) {
- #do a specific search
- $operator = 'LIKE' if $operator eq '=' and $right =~ /%/;
- my $sth = $dbh->prepare(
-"SELECT biblionumbers,value FROM nozebra WHERE server=? AND indexname=? AND value $operator ?"
- );
- warn "$left / $operator / $right\n" if $DEBUG;
-
- # split each word, query the DB and build the biblionumbers result
- #sanitizing leftpart
- $left =~ s/^\s+|\s+$//;
- foreach ( split / /, $right ) {
- my $biblionumbers;
- $_ =~ s/^\s+|\s+$//;
- next unless $_;
- warn "EXECUTE : $server, $left, $_" if $DEBUG;
- $sth->execute( $server, $left, $_ )
- or warn "execute failed: $!";
- while ( my ( $line, $value ) = $sth->fetchrow ) {
-
-# if we are dealing with a numeric value, use only numeric results (in case of >=, <=, > or <)
-# otherwise, fill the result
- $biblionumbers .= $line
- unless ( $right =~ /^\d+$/ && $value =~ /\D/ );
- warn "result : $value "
- . ( $right =~ /\d/ ) . "=="
- . ( $value =~ /\D/?$line:"" ) if $DEBUG; #= $line";
- }
-
-# do a AND with existing list if there is one, otherwise, use the biblionumbers list as 1st result list
- if ($results) {
- warn "NZAND" if $DEBUG;
- $results = NZoperatorAND($biblionumbers,$results);
- } else {
- $results = $biblionumbers;
- }
- }
- }
- else {
- #do a complete search (all indexes), if index='kw' do complete search too.
- my $sth = $dbh->prepare(
-"SELECT biblionumbers FROM nozebra WHERE server=? AND value LIKE ?"
- );
-
- # split each word, query the DB and build the biblionumbers result
- foreach ( split / /, $string ) {
- next if C4::Context->stopwords->{ uc($_) }; # skip if stopword
- warn "search on all indexes on $_" if $DEBUG;
- my $biblionumbers;
- next unless $_;
- $sth->execute( $server, $_ );
- while ( my $line = $sth->fetchrow ) {
- $biblionumbers .= $line;
- }
-
-# do a AND with existing list if there is one, otherwise, use the biblionumbers list as 1st result list
- if ($results) {
- $results = NZoperatorAND($biblionumbers,$results);
- }
- else {
- warn "NEW RES for $_ = $biblionumbers" if $DEBUG;
- $results = $biblionumbers;
- }
- }
- }
- warn "return : $results for LEAF : $string" if $DEBUG;
- return $results;
- }
- warn "---------\nLeave NZanalyse\n---------" if $DEBUG;
-}
-
-sub NZoperatorAND{
- my ($rightresult, $leftresult)=@_;
-
- my @leftresult = split /;/, $leftresult;
- warn " @leftresult / $rightresult \n" if $DEBUG;
-
- # my @rightresult = split /;/,$leftresult;
- my $finalresult;
-
-# parse the left results, and if the biblionumber exist in the right result, save it in finalresult
-# the result is stored twice, to have the same weight for AND than OR.
-# example : TWO : 61,61,64,121 (two is twice in the biblio #61) / TOWER : 61,64,130
-# result : 61,61,61,61,64,64 for two AND tower : 61 has more weight than 64
- foreach (@leftresult) {
- my $value = $_;
- my $countvalue;
- ( $value, $countvalue ) = ( $1, $2 ) if ($value=~/(.*)-(\d+)$/);
- if ( $rightresult =~ /\Q$value\E-(\d+);/ ) {
- $countvalue = ( $1 > $countvalue ? $countvalue : $1 );
- $finalresult .=
- "$value-$countvalue;$value-$countvalue;";
- }
- }
- warn "NZAND DONE : $finalresult \n" if $DEBUG;
- return $finalresult;
-}
-
-sub NZoperatorOR{
- my ($rightresult, $leftresult)=@_;
- return $rightresult.$leftresult;
-}
-
-sub NZoperatorNOT{
- my ($leftresult, $rightresult)=@_;
-
- my @leftresult = split /;/, $leftresult;
-
- # my @rightresult = split /;/,$leftresult;
- my $finalresult;
- foreach (@leftresult) {
- my $value=$_;
- $value=$1 if $value=~m/(.*)-\d+$/;
- unless ($rightresult =~ "$value-") {
- $finalresult .= "$_;";
- }
- }
- return $finalresult;
-}
-
-=head2 NZorder
-
- $finalresult = NZorder($biblionumbers, $ordering,$results_per_page,$offset);
-
- TODO :: Description
-
-=cut
-
-sub NZorder {
- my ( $biblionumbers, $ordering, $results_per_page, $offset ) = @_;
- warn "biblionumbers = $biblionumbers and ordering = $ordering\n" if $DEBUG;
-
- # order title asc by default
- # $ordering = '1=36 <i' unless $ordering;
- $results_per_page = 20 unless $results_per_page;
- $offset = 0 unless $offset;
- my $dbh = C4::Context->dbh;
-
- #
- # order by POPULARITY
- #
- if ( $ordering =~ /popularity/ ) {
- my %result;
- my %popularity;
-
- # popularity is not in MARC record, it's builded from a specific query
- my $sth =
- $dbh->prepare("select sum(issues) from items where biblionumber=?");
- foreach ( split /;/, $biblionumbers ) {
- my ( $biblionumber, $title ) = split /,/, $_;
- $result{$biblionumber} = GetMarcBiblio($biblionumber);
- $sth->execute($biblionumber);
- my $popularity = $sth->fetchrow || 0;
-
-# hint : the key is popularity.title because we can have
-# many results with the same popularity. In this case, sub-ordering is done by title
-# we also have biblionumber to avoid bug for 2 biblios with the same title & popularity
-# (un-frequent, I agree, but we won't forget anything that way ;-)
- $popularity{ sprintf( "%10d", $popularity ) . $title
- . $biblionumber } = $biblionumber;
- }
-
- # sort the hash and return the same structure as GetRecords (Zebra querying)
- my $result_hash;
- my $numbers = 0;
- if ( $ordering eq 'popularity_dsc' ) { # sort popularity DESC
- foreach my $key ( sort { $b cmp $a } ( keys %popularity ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] =
- $result{ $popularity{$key} }->as_usmarc();
- }
- }
- else { # sort popularity ASC
- foreach my $key ( sort ( keys %popularity ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] =
- $result{ $popularity{$key} }->as_usmarc();
- }
- }
- my $finalresult = ();
- $result_hash->{'hits'} = $numbers;
- $finalresult->{'biblioserver'} = $result_hash;
- return $finalresult;
-
- #
- # ORDER BY author
- #
- }
- elsif ( $ordering =~ /author/ ) {
- my %result;
- foreach ( split /;/, $biblionumbers ) {
- my ( $biblionumber, $title ) = split /,/, $_;
- my $record = GetMarcBiblio($biblionumber);
- my $author;
- if ( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
- $author = $record->subfield( '200', 'f' );
- $author = $record->subfield( '700', 'a' ) unless $author;
- }
- else {
- $author = $record->subfield( '100', 'a' );
- }
-
-# hint : the result is sorted by title.biblionumber because we can have X biblios with the same title
-# and we don't want to get only 1 result for each of them !!!
- $result{ $author . $biblionumber } = $record;
- }
-
- # sort the hash and return the same structure as GetRecords (Zebra querying)
- my $result_hash;
- my $numbers = 0;
- if ( $ordering eq 'author_za' || $ordering eq 'author_dsc' ) { # sort by author desc
- foreach my $key ( sort { $b cmp $a } ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] =
- $result{$key}->as_usmarc();
- }
- }
- else { # sort by author ASC
- foreach my $key ( sort ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] =
- $result{$key}->as_usmarc();
- }
- }
- my $finalresult = ();
- $result_hash->{'hits'} = $numbers;
- $finalresult->{'biblioserver'} = $result_hash;
- return $finalresult;
-
- #
- # ORDER BY callnumber
- #
- }
- elsif ( $ordering =~ /callnumber/ ) {
- my %result;
- foreach ( split /;/, $biblionumbers ) {
- my ( $biblionumber, $title ) = split /,/, $_;
- my $record = GetMarcBiblio($biblionumber);
- my $callnumber;
- my $frameworkcode = GetFrameworkCode($biblionumber);
- my ( $callnumber_tag, $callnumber_subfield ) = GetMarcFromKohaField( 'items.itemcallnumber', $frameworkcode);
- ( $callnumber_tag, $callnumber_subfield ) = GetMarcFromKohaField('biblioitems.callnumber', $frameworkcode)
- unless $callnumber_tag;
- if ( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
- $callnumber = $record->subfield( '200', 'f' );
- } else {
- $callnumber = $record->subfield( '100', 'a' );
- }
-
-# hint : the result is sorted by title.biblionumber because we can have X biblios with the same title
-# and we don't want to get only 1 result for each of them !!!
- $result{ $callnumber . $biblionumber } = $record;
- }
-
- # sort the hash and return the same structure as GetRecords (Zebra querying)
- my $result_hash;
- my $numbers = 0;
- if ( $ordering eq 'call_number_dsc' ) { # sort by title desc
- foreach my $key ( sort { $b cmp $a } ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] =
- $result{$key}->as_usmarc();
- }
- }
- else { # sort by title ASC
- foreach my $key ( sort { $a cmp $b } ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] =
- $result{$key}->as_usmarc();
- }
- }
- my $finalresult = ();
- $result_hash->{'hits'} = $numbers;
- $finalresult->{'biblioserver'} = $result_hash;
- return $finalresult;
- }
- elsif ( $ordering =~ /pubdate/ ) { #pub year
- my %result;
- foreach ( split /;/, $biblionumbers ) {
- my ( $biblionumber, $title ) = split /,/, $_;
- my $record = GetMarcBiblio($biblionumber);
- my ( $publicationyear_tag, $publicationyear_subfield ) =
- GetMarcFromKohaField( 'biblioitems.publicationyear', '' );
- my $publicationyear =
- $record->subfield( $publicationyear_tag,
- $publicationyear_subfield );
-
-# hint : the result is sorted by title.biblionumber because we can have X biblios with the same title
-# and we don't want to get only 1 result for each of them !!!
- $result{ $publicationyear . $biblionumber } = $record;
- }
-
- # sort the hash and return the same structure as GetRecords (Zebra querying)
- my $result_hash;
- my $numbers = 0;
- if ( $ordering eq 'pubdate_dsc' ) { # sort by pubyear desc
- foreach my $key ( sort { $b cmp $a } ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] =
- $result{$key}->as_usmarc();
- }
- }
- else { # sort by pub year ASC
- foreach my $key ( sort ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] =
- $result{$key}->as_usmarc();
- }
- }
- my $finalresult = ();
- $result_hash->{'hits'} = $numbers;
- $finalresult->{'biblioserver'} = $result_hash;
- return $finalresult;
-
- #
- # ORDER BY title
- #
- }
- elsif ( $ordering =~ /title/ ) {
-
-# the title is in the biblionumbers string, so we just need to build a hash, sort it and return
- my %result;
- foreach ( split /;/, $biblionumbers ) {
- my ( $biblionumber, $title ) = split /,/, $_;
-
-# hint : the result is sorted by title.biblionumber because we can have X biblios with the same title
-# and we don't want to get only 1 result for each of them !!!
-# hint & speed improvement : we can order without reading the record
-# so order, and read records only for the requested page !
- $result{ $title . $biblionumber } = $biblionumber;
- }
-
- # sort the hash and return the same structure as GetRecords (Zebra querying)
- my $result_hash;
- my $numbers = 0;
- if ( $ordering eq 'title_az' ) { # sort by title desc
- foreach my $key ( sort ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] = $result{$key};
- }
- }
- else { # sort by title ASC
- foreach my $key ( sort { $b cmp $a } ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] = $result{$key};
- }
- }
-
- # limit the $results_per_page to result size if it's more
- $results_per_page = $numbers - 1 if $numbers < $results_per_page;
-
- # for the requested page, replace biblionumber by the complete record
- # speed improvement : avoid reading too much things
- for (
- my $counter = $offset ;
- $counter <= $offset + $results_per_page ;
- $counter++
- )
- {
- $result_hash->{'RECORDS'}[$counter] =
- GetMarcBiblio( $result_hash->{'RECORDS'}[$counter] )->as_usmarc;
- }
- my $finalresult = ();
- $result_hash->{'hits'} = $numbers;
- $finalresult->{'biblioserver'} = $result_hash;
- return $finalresult;
- }
- else {
-
-#
-# order by ranking
-#
-# we need 2 hashes to order by ranking : the 1st one to count the ranking, the 2nd to order by ranking
- my %result;
- my %count_ranking;
- foreach ( split /;/, $biblionumbers ) {
- my ( $biblionumber, $title ) = split /,/, $_;
- $title =~ /(.*)-(\d)/;
-
- # get weight
- my $ranking = $2;
-
-# note that we + the ranking because ranking is calculated on weight of EACH term requested.
-# if we ask for "two towers", and "two" has weight 2 in biblio N, and "towers" has weight 4 in biblio N
-# biblio N has ranking = 6
- $count_ranking{$biblionumber} += $ranking;
- }
-
-# build the result by "inverting" the count_ranking hash
-# hing : as usual, we don't order by ranking only, to avoid having only 1 result for each rank. We build an hash on concat(ranking,biblionumber) instead
-# warn "counting";
- foreach ( keys %count_ranking ) {
- $result{ sprintf( "%10d", $count_ranking{$_} ) . '-' . $_ } = $_;
- }
-
- # sort the hash and return the same structure as GetRecords (Zebra querying)
- my $result_hash;
- my $numbers = 0;
- foreach my $key ( sort { $b cmp $a } ( keys %result ) ) {
- $result_hash->{'RECORDS'}[ $numbers++ ] = $result{$key};
- }
-
- # limit the $results_per_page to result size if it's more
- $results_per_page = $numbers - 1 if $numbers < $results_per_page;
-
- # for the requested page, replace biblionumber by the complete record
- # speed improvement : avoid reading too much things
- for (
- my $counter = $offset ;
- $counter <= $offset + $results_per_page ;
- $counter++
- )
- {
- $result_hash->{'RECORDS'}[$counter] =
- GetMarcBiblio( $result_hash->{'RECORDS'}[$counter] )->as_usmarc
- if $result_hash->{'RECORDS'}[$counter];
- }
- my $finalresult = ();
- $result_hash->{'hits'} = $numbers;
- $finalresult->{'biblioserver'} = $result_hash;
- return $finalresult;
- }
-}
=head2 enabled_staff_search_views
return $sth->fetchall_hashref({});
}
+sub PurgeSearchHistory{
+ my ($pSearchhistory)=@_;
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare("DELETE FROM search_history WHERE time < DATE_SUB( NOW(), INTERVAL ? DAY )");
+ $sth->execute($pSearchhistory) or die $dbh->errstr;
+}
+
=head2 z3950_search_args
$arrayref = z3950_search_args($matchpoints)
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;
}
# 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)