# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
-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::Members qw(GetHideLostItemsPreference);
-use C4::XSLT;
-use C4::Branch;
-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 YAML;
+use Koha::Logger;
+use Koha::Patrons;
+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);
-# set the version for version checking
+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
=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
sub FindDuplicate {
my ($record) = @_;
my $dbh = C4::Context->dbh;
- my $result = TransformMarcToKoha( $dbh, $record, '' );
+ my $result = TransformMarcToKoha( $record, '' );
my $sth;
my $query;
- my $search;
- my $type;
- my ( $biblionumber, $title );
# search duplicate on ISBN, easy and fast..
# ... normalize first
$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;
$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}) {
$possible_duplicate_record
);
- my $result = TransformMarcToKoha( $dbh, $marcrecord, '' );
+ my $result = TransformMarcToKoha( $marcrecord, '' );
# FIXME :: why 2 $biblionumber ?
if ($result) {
=head2 SimpleSearch
-( $error, $results, $total_hits ) = SimpleSearch( $query, $offset, $max_results, [@servers] );
+( $error, $results, $total_hits ) = SimpleSearch( $query, $offset, $max_results, [@servers], [%options] );
This function provides a simple search API on the bibliographic catalog
* @servers is optional. Defaults to biblioserver as found in koha-conf.xml
* $offset - If present, represents the number of records at the beginning to omit. Defaults to 0
* $max_results - if present, determines the maximum number of records to fetch. undef is All. defaults to undef.
+ * %options is optional. (e.g. "skip_normalize" allows you to skip changing : to = )
=item C<Return:>
for my $r ( @{$marcresults} ) {
my $marcrecord = MARC::File::USMARC::decode($r);
- my $biblio = TransformMarcToKoha(C4::Context->dbh,$marcrecord,q{});
+ my $biblio = TransformMarcToKoha($marcrecord,q{});
#build the iarray of hashs for the template.
push @results, {
=cut
sub SimpleSearch {
- my ( $query, $offset, $max_results, $servers ) = @_;
+ my ( $query, $offset, $max_results, $servers, %options ) = @_;
return ( 'No query entered', undef, undef ) unless $query;
# FIXME hardcoded value. See catalog/search.pl & opac-search.pl too.
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;
- $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]);
- }
+ $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
( undef, $results_hashref, \@facets_loop ) = getRecords (
$koha_query, $simple_query, $sort_by_ref, $servers_ref,
- $results_per_page, $offset, $expanded_facet, $branches,$itemtypes,
- $query_type, $scan
+ $results_per_page, $offset, $branches, $itemtypes,
+ $query_type, $scan, $opac
);
The all singing, all dancing, multi-server, asynchronous, scanning,
searching, record nabbing, facet-building
-See verbse embedded documentation.
+See verbose embedded documentation.
=cut
sub getRecords {
my (
$koha_query, $simple_query, $sort_by_ref, $servers_ref,
- $results_per_page, $offset, $expanded_facet, $branches,
- $itemtypes, $query_type, $scan, $opac
+ $results_per_page, $offset, $branches, $itemtypes,
+ $query_type, $scan, $opac
) = @_;
my @servers = @$servers_ref;
my @sort_by = @$sort_by_ref;
+ $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 };
+
# Initialize variables for the faceted results objects
my $facets_counter = {};
my $facets_info = {};
# 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 ($@) {
}
for ( my $j = $offset ; $j < $times ; $j++ ) {
- my $records_hash;
my $record;
## Check if it's an index scan
# BUILD FACETS
if ( $servers[ $i - 1 ] =~ /biblioserver/ ) {
for my $link_value (
- sort { $facets_counter->{$b} <=> $facets_counter->{$a} }
- keys %$facets_counter
+ sort { $a cmp $b } keys %$facets_counter
)
{
- my $expandable;
- my $number_of_facets;
my @this_facets_array;
for my $one_facet (
sort {
} keys %{ $facets_counter->{$link_value} }
)
{
- $number_of_facets++;
- if ( ( $number_of_facets <= 5 )
- || ( $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 = "*";
- }
+ 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'};
}
-
- # 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}
- ->{translated_description};
- }
+ else {
+ $facet_label_value = "*";
}
+ }
- # also, if it's a location code, use the name instead of the code
- if ( $link_value =~ /location/ ) {
+ # 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 =
- GetKohaAuthorisedValueLib( 'LOC',
- $one_facet, $opac );
+ $itemtypes->{$one_facet}
+ ->{translated_description};
}
+ }
- # 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);
+ # also, if it's a location code, use the name instead of the code
+ if ( $link_value =~ /location/ ) {
+ # TODO Retrieve all authorised values at once, instead of 1 query per entry
+ my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $one_facet });
+ $facet_label_value = $av->count ? $av->next->opac_description : '';
}
- }
- # handle expanded option
- unless ( $facets_info->{$link_value}->{'expanded'} ) {
- $expandable = 1
- if ( ( $number_of_facets > 5 )
- && ( $expanded_facet ne $link_value ) );
+ # also, if it's a collection code, use the name instead of the code
+ if ( $link_value =~ /ccode/ ) {
+ # TODO Retrieve all authorised values at once, instead of 1 query per entry
+ my $av = Koha::AuthorisedValues->search({ category => 'CCODE', authorised_value => $one_facet });
+ $facet_label_value = $av->count ? $av->next->opac_description : '';
+ }
+
+ # 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);
}
+
push @facets_loop,
{
type_link_value => $link_value,
. $facets_info->{$link_value}->{'label_value'} =>
1,
facets => \@this_facets_array,
- expandable => $expandable,
- expand => $link_value,
}
unless (
(
}
}
);
+
+ # 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} } ];
+ }
+ }
+
return ( undef, $results_hashref, \@facets_loop );
}
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 ) {
+ if ( $use_zebra_facets ) {
$facets = _get_facets_from_zebra( $rs );
} else {
$facets = _get_facets_from_records( $rs );
next if $field->indicator(1) eq 'z';
my $data = $field->as_string( $subfield_letters, $facet->{ sep } );
+ $data =~ s/\s*(?<!\p{Uppercase})[.\-,;]*\s*$//;
- unless ( grep { /^\Q$data\E$/ } @used_datas ) {
+ unless ( grep { $_ eq $data } @used_datas ) {
push @used_datas, $data;
$facets_counter->{ $facet->{ idx } }->{ $data }++;
}
my $facets = {};
foreach my $term ( @terms ) {
my $facet_value = $term->textContent;
+ $facet_value =~ s/\s*(?<!\p{Uppercase})[.\-,;]*\s*$//;
$facet_value =~ s/\Q$internal_sep\E/$sep/ if defined $sep;
- $facets->{ $facet_value } = $term->getAttribute( 'occur' );
+ $facets->{ $facet_value } += $term->getAttribute( 'occur' );
}
return $facets;
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,
- $results_per_page, $offset, $expanded_facet, $branches,
- $query_type, $scan
- ) = @_;
-
- 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 ) = @_;
require Lingua::Stem::Snowball ;
my $stemmed_operand=q{};
+ # Stemmer needs language
+ return $operand unless $lang;
+
# 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
# can contain the letter "X" - for example, _build_stemmend_operand would reduce
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;
}
$weighted_query .= "an=\"$operand\"";
}
+ # If the index is numeric, don't autoquote it.
+ elsif ( $index =~ /,st-numeric$/ ) {
+ $weighted_query .= " $index=$operand";
+ }
+
# If the index already has more than one qualifier, wrap the operand
# in quotes and pass it back (assumption is that the user knows what they
# are doing and won't appreciate us mucking up their query
'an',
'Any',
'at',
+ 'arl',
+ 'arp',
'au',
'aub',
'aud',
'Heading-use-subject-added-entry',
'Host-item',
'id-other',
+ 'ident',
+ 'Identifier-standard',
'Illustration-code',
'Index-term-genre',
'Index-term-uncontrolled',
+ 'Interest-age-level',
+ 'Interest-grade-level',
'ISBN',
'isbn',
'ISSN',
'LC-card-number',
'lcn',
'lex',
+ 'lexile-number',
'llength',
'ln',
'ln-audio',
'notes',
'ns',
'nt',
+ 'Other-control-number',
'pb',
'Personal-name',
'Personal-name-heading',
'Publisher',
'Provider',
'pv',
+ 'Reading-grade-level',
'Record-control-number',
'rcn',
'Record-type',
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,
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 : ();
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
# This is needed otherwise ccl= and &limit won't work together, and
# this happens when selecting a subject on the opac-detail page
@limits = grep {!/^$/} @limits;
+ my $original_q = $q; # without available part
+ unless ( grep { $_ eq 'available' } @limits ) {
+ $q =~ s| and \( \(allrecords,AlwaysMatches=''\) and \(not-onloan-count,st-numeric >= 1\) and \(lost,st-numeric=0\) \)||;
+ $original_q = $q;
+ }
if ( @limits ) {
- $q .= ' and '.join(' and ', @limits);
+ if ( grep { $_ eq 'available' } @limits ) {
+ $q .= q| and ( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) )|;
+ @limits = grep {!/^available$/} @limits;
+ }
+ $q .= ' and '.join(' and ', @limits) if @limits;
}
- return ( undef, $q, $q, "q=ccl=".uri_escape_utf8($q), $q, '', '', '', 'ccl' );
+ 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' );
}
for ( my $i = 0 ; $i <= @operands ; $i++ ) {
# COMBINE OPERANDS, INDEXES AND OPERATORS
- if ( $operands[$i] ) {
+ if ( ($operands[$i] // '') ne '' ) {
$operands[$i]=~s/^\s+//;
# A flag to determine whether or not to add the index to the query
$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.
- # Date of Publication
- if ( $index =~ /yr/ ) {
+ # Search ranges: Date of Publication, st-numeric
+ if ( $index =~ /(yr|st-numeric)/ ) {
#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 = 0;
}
# ISBN,ISSN,Standard Number, don't need special treatment
- elsif ( $index eq 'nb' || $index eq 'ns' ) {
+ elsif ( $index eq 'nb' || $index eq 'ns' || $index eq 'hi' ) {
(
$stemming, $auto_truncation,
$weight_fields, $fuzzy_enabled
if ( C4::Context->preference("SearchWithISBNVariations") ) {
my @isbns = C4::Koha::GetVariationsOfISBN( $operand );
$operands[$i] = $operand = '(nb=' . join(' OR nb=', @isbns) . ')';
- $indexes[$i] = $index = '';
+ $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";
}
$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 (
}
}
$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,
query_desc => $query_desc,
operator => ($operators[ $i - 1 ]) ? $operators[ $i - 1 ] : '',
parsed_operand => $operand,
- original_operand => ($operands[$i]) ? $operands[$i] : '',
+ original_operand => $operands[$i] // '',
index => $index,
index_plus => $index_plus,
indexes_set => $indexes_set,
} #/if $operands
} # /for
}
- warn "QUERY BEFORE LIMITS: >$query<" if $DEBUG;
+ Koha::Logger->get->debug("QUERY BEFORE LIMITS: >$query<");
# add limits
my %group_OR_limits;
## 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='' not onloan,AlwaysMatches='') and (lost,st-numeric=0) )"; #or ( allrecords,AlwaysMatches='' not lost,AlwaysMatches='')) )";
+"( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) )";
$limit_cgi .= "&limit=available";
$limit_desc .= "";
}
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."'";
+ $this_limit = $k.':"'.$v.'"';
}
$group_OR_limits{$k} .= " or " if $group_OR_limits{$k};
$limit_cgi .= "&limit=" . uri_escape_utf8($this_limit);
if ($this_limit =~ /^branch:(.+)/) {
my $branchcode = $1;
- my $branchname = GetBranchName($branchcode);
- if (defined $branchname) {
- $limit_desc .= " branch:$branchname";
+ my $library = Koha::Libraries->find( $branchcode );
+ if (defined $library) {
+ $limit_desc .= " branch:" . $library->branchname;
} else {
$limit_desc .= " $this_limit";
}
$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
# 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,
$query_desc, $limit, $limit_cgi, $limit_desc,
$params->{query_cgi} .= "&q=".uri_escape_utf8($params->{original_operand}) if $params->{original_operand};
#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
# 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;
require C4::Items;
- $search_context = 'opac' if !$search_context || $search_context ne 'intranet';
+ $search_context->{'interface'} = 'opac' if !$search_context->{'interface'} || $search_context->{'interface'} ne 'intranet';
my ($is_opac, $hidelostitems);
- if ($search_context eq 'opac') {
+ if ($search_context->{'interface'} eq 'opac') {
$hidelostitems = C4::Context->preference('hidelostitems');
$is_opac = 1;
}
+ my $record_processor = Koha::RecordProcessor->new({
+ filters => 'ViewPolicy'
+ });
+
#Build branchnames hash
- #find branchname
- #get branch information.....
- my %branches;
- my $bsth =$dbh->prepare("SELECT branchcode,branchname FROM branches"); # FIXME : use C4::Branch::GetBranches
- $bsth->execute();
- while ( my $bdata = $bsth->fetchrow_hashref ) {
- $branches{ $bdata->{'branchcode'} } = $bdata->{'branchname'};
- }
+ my %branches = map { $_->branchcode => $_->branchname } Koha::Libraries->search({}, { order_by => 'branchname' });
+
# FIXME - We build an authorised values hash here, using the default framework
# though it is possible to have different authvals for different fws.
- my $shelflocations =GetKohaAuthorisedValues('items.location','');
+ my $shelflocations =
+ { map { $_->{authorised_value} => $_->{lib} } Koha::AuthorisedValues->get_descriptions_by_koha_field( { frameworkcode => '', kohafield => 'items.location' } ) };
# get notforloan authorised value list (see $shelflocations FIXME)
- my $notforloan_authorised_value = GetAuthValCode('items.notforloan','');
+ my $av = Koha::MarcSubfieldStructures->search({ frameworkcode => '', kohafield => 'items.notforloan', authorised_value => [ -and => {'!=' => undef }, {'!=' => ''}] });
+ my $notforloan_authorised_value = $av->count ? $av->next->authorised_value : undef;
#Get itemtype hash
- my %itemtypes = %{ GetItemTypes() };
+ my $itemtypes = Koha::ItemTypes->search_with_localization;
+ my %itemtypes = map { $_->{itemtype} => $_ } @{ $itemtypes->unblessed };
#search item field code
- my ($itemtag, undef) = &GetMarcFromKohaField( "items.itemnumber", "" );
+ my ($itemtag, undef) = &GetMarcFromKohaField( "items.itemnumber" );
## find column names of items related to MARC
my %subfieldstosearch;
my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
for my $column ( @columns ) {
my ( $tagfield, $tagsubfield ) =
- &GetMarcFromKohaField( "items." . $column, "" );
+ &GetMarcFromKohaField( "items." . $column );
if ( defined $tagsubfield ) {
$subfieldstosearch{$column} = $tagsubfield;
}
}
# 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");
# We get the biblionumber position in MARC
- my ($bibliotag,$bibliosubf)=GetMarcFromKohaField('biblio.biblionumber','');
+ 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
+ = ( defined $userenv and $userenv->{number} )
+ ? Koha::Patrons->find( $userenv->{number} )
+ : undef;
+ my $patron_category_hide_lost_items = ($logged_in_user) ? $logged_in_user->category->hidelostitems : 0;
# loop through all of the records we've retrieved
for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) {
: GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf));
SetUTF8Flag($marcrecord);
- my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, $fw );
- $oldbiblio->{subtitle} = GetRecordValue('subtitle', $marcrecord, $fw);
+ my $oldbiblio = TransformMarcToKoha( $marcrecord, $fw, '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->{'authorised_value_images'} = ($search_context eq 'opac' && C4::Context->preference('AuthorisedValueImages')) || ($search_context eq 'intranet' && C4::Context->preference('StaffAuthorisedValueImages')) ? C4::Items::get_authorised_value_images( C4::Biblio::get_biblio_authorised_values( $oldbiblio->{'biblionumber'}, $marcrecord ) ) : [];
$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 $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{};
+
+ # FIXME: this is only used in the deprecated non-XLST opac results
+ if ( !$xslfile && $is_opac && $itemtype && $itemtype->{summary} ) {
my $summary = $itemtypes{ $oldbiblio->{itemtype} }->{summary};
my @fields = $marcrecord->fields();
# Pull out the items fields
my @fields = $marcrecord->field($itemtag);
my $marcflavor = C4::Context->preference("marcflavour");
+
# adding linked items that belong to host records
- my $analyticsfield = '773';
- if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
- $analyticsfield = '773';
- } elsif ($marcflavor eq 'UNIMARC') {
- $analyticsfield = '461';
- }
- foreach my $hostfield ( $marcrecord->field($analyticsfield)) {
- my $hostbiblionumber = $hostfield->subfield("0");
- my $linkeditemnumber = $hostfield->subfield("9");
- if(!$hostbiblionumber eq undef){
- my $hostbiblio = GetMarcBiblio($hostbiblionumber, 1);
- my ($itemfield, undef) = GetMarcFromKohaField( 'items.itemnumber', GetFrameworkCode($hostbiblionumber) );
- if(!$hostbiblio eq undef){
- my @hostitems = $hostbiblio->field($itemfield);
- foreach my $hostitem (@hostitems){
- if ($hostitem->subfield("9") eq $linkeditemnumber){
- my $linkeditem =$hostitem;
- # append linked items if they exist
- if (!$linkeditem eq undef){
- push (@fields, $linkeditem);}
+ if ( C4::Context->preference('EasyAnalyticalRecords') ) {
+ my $analyticsfield = '773';
+ if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
+ $analyticsfield = '773';
+ } elsif ($marcflavor eq 'UNIMARC') {
+ $analyticsfield = '461';
+ }
+ foreach my $hostfield ( $marcrecord->field($analyticsfield)) {
+ my $hostbiblionumber = $hostfield->subfield("0");
+ my $linkeditemnumber = $hostfield->subfield("9");
+ if( $hostbiblionumber ) {
+ my $linkeditemmarc = C4::Items::GetMarcItem( $hostbiblionumber, $linkeditemnumber );
+ if ($linkeditemmarc) {
+ my $linkeditemfield = $linkeditemmarc->field($itemtag);
+ if ($linkeditemfield) {
+ push( @fields, $linkeditemfield );
}
}
}
foreach my $code ( keys %subfieldstosearch ) {
$item->{$code} = $field->subfield( $subfieldstosearch{$code} );
}
- $item->{description} = $itemtypes{ $item->{itype} }{translated_description};
+ $item->{description} = $itemtypes{ $item->{itype} }{translated_description} if $item->{itype};
# OPAC hidden items
if ($is_opac) {
next;
}
# hidden based on OpacHiddenItems syspref
- my @hi = C4::Items::GetHiddenItemnumbers($item);
+ my @hi = C4::Items::GetHiddenItemnumbers({ items=> [ $item ], borcat => $search_context->{category} });
if (scalar @hi) {
push @hiddenitems, @hi;
$hideatopac_count++;
$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
- my $userenv = C4::Context->userenv;
if ( $item->{onloan}
- && !( C4::Members::GetHideLostItemsPreference( $userenv->{'number'} ) && $item->{itemlost} ) )
+ and $logged_in_user
+ and !( $patron_category_hide_lost_items and $item->{itemlost} ) )
{
$onloan_count++;
my $key = $prefix . $item->{onloan} . $item->{barcode};
- $onloan_items->{$key}->{due_date} = output_pref( { dt => dt_from_string( $item->{onloan} ), dateonly => 1 } );
+ $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} =
- getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
+ getitemtypeimagelocation( $search_context->{'interface'}, $itemtypes{ $item->{itype} }->{imageurl} );
# if something's checked out and lost, mark it as 'long overdue'
if ( $item->{itemlost} ) {
# 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 ) {
|| $item->{damaged}
|| $item->{notforloan}
|| $reservestatus eq 'Waiting'
- || ($transfertwhen ne ''))
+ || ($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};
+ $item->{status} = ($item->{withdrawn}//q{}) . "-" . ($item->{itemlost}//q{}) . "-" . ($item->{damaged}//q{}) . "-" . ($item->{notforloan}//q{});
# can place a hold on a item if
# not lost nor withdrawn
$other_items->{$key}->{intransit} = ( $transfertwhen ne '' ) ? 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}->{description} = $item->{description};
- $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
+ $other_items->{$key}->{count}++ if $item->{$hbranch};
+ $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}//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, $itemtypes{ $item->{itype} }->{imageurl} );
+ $available_items->{$prefix}->{count}++ if $item->{$hbranch};
+ foreach (qw(branchname itemcallnumber description)) {
+ $available_items->{$prefix}->{$_} = $item->{$_};
+ }
+ $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;
}
}
# XSLT processing of some stuff
- 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
+ # we fetched the sysprefs already before the loop through all retrieved record!
+ if (!$scan && $xslfile) {
+ $record_processor->options({
+ frameworkcode => $fw,
+ interface => $search_context->{'interface'}
+ });
+
+ $record_processor->process($marcrecord);
+ $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, $xslsyspref, 1, \@hiddenitems, $sysxml, $xslfile, $lang, $xslt_variables);
}
# 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;
}
}
$oldbiblio->{norequests} = 1 unless $can_place_holds;
- $oldbiblio->{itemsplural} = 1 if $items_count > 1;
$oldbiblio->{items_count} = $items_count;
$oldbiblio->{available_items_loop} = \@available_items_loop;
$oldbiblio->{onloan_items_loop} = \@onloan_items_loop;
$oldbiblio->{'alternateholdings_count'} = $alternateholdingscount;
}
+ $oldbiblio->{biblio_object} = Koha::Biblios->find( $oldbiblio->{biblionumber} );
+
push( @newresults, $oldbiblio );
}
return @newresults;
}
-=head2 SearchAcquisitions
- Search for acquisitions
-=cut
-
-sub SearchAcquisitions{
- my ($datebegin, $dateend, $itemtypes,$criteria, $orderby) = @_;
-
- my $dbh=C4::Context->dbh;
- # Variable initialization
- my $str=qq|
- SELECT marcxml
- FROM biblio
- LEFT JOIN biblioitems ON biblioitems.biblionumber=biblio.biblionumber
- LEFT JOIN items ON items.biblionumber=biblio.biblionumber
- WHERE dateaccessioned BETWEEN ? AND ?
- |;
-
- my (@params,@loopcriteria);
-
- push @params, $datebegin->output("iso");
- push @params, $dateend->output("iso");
-
- if (scalar(@$itemtypes)>0 and $criteria ne "itemtype" ){
- if(C4::Context->preference("item-level_itypes")){
- $str .= "AND items.itype IN (?".( ',?' x scalar @$itemtypes - 1 ).") ";
- }else{
- $str .= "AND biblioitems.itemtype IN (?".( ',?' x scalar @$itemtypes - 1 ).") ";
- }
- push @params, @$itemtypes;
- }
-
- if ($criteria =~/itemtype/){
- if(C4::Context->preference("item-level_itypes")){
- $str .= "AND items.itype=? ";
- }else{
- $str .= "AND biblioitems.itemtype=? ";
- }
-
- if(scalar(@$itemtypes) == 0){
- my $itypes = GetItemTypes();
- for my $key (keys %$itypes){
- push @$itemtypes, $key;
- }
- }
-
- @loopcriteria= @$itemtypes;
- }elsif ($criteria=~/itemcallnumber/){
- $str .= "AND (items.itemcallnumber LIKE CONCAT(?,'%')
- OR items.itemcallnumber is NULL
- OR items.itemcallnumber = '')";
-
- @loopcriteria = ("AA".."ZZ", "") unless (scalar(@loopcriteria)>0);
- }else {
- $str .= "AND biblio.title LIKE CONCAT(?,'%') ";
- @loopcriteria = ("A".."z") unless (scalar(@loopcriteria)>0);
- }
-
- if ($orderby =~ /date_desc/){
- $str.=" ORDER BY dateaccessioned DESC";
- } else {
- $str.=" ORDER BY title";
- }
-
- my $qdataacquisitions=$dbh->prepare($str);
-
- my @loopacquisitions;
- foreach my $value(@loopcriteria){
- push @params,$value;
- my %cell;
- $cell{"title"}=$value;
- $cell{"titlecode"}=$value;
-
- eval{$qdataacquisitions->execute(@params);};
-
- if ($@){ warn "recentacquisitions Error :$@";}
- else {
- my @loopdata;
- while (my $data=$qdataacquisitions->fetchrow_hashref){
- push @loopdata, {"summary"=>GetBiblioSummary( $data->{'marcxml'} ) };
- }
- $cell{"loopdata"}=\@loopdata;
- }
- push @loopacquisitions,\%cell if (scalar(@{$cell{loopdata}})>0);
- pop @params;
- }
- $qdataacquisitions->finish;
- return \@loopacquisitions;
-}
-
=head2 enabled_staff_search_views
%hash = enabled_staff_search_views()
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({});
=head2 new_record_from_zebra
-Given raw data from a Zebra result set, return a MARC::Record object
+Given raw data from a searchengine 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
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.
+If we are using elasticsearch, it'll already be a MARC::Record and this
+function needs a new name.
+
=cut
sub new_record_from_zebra {
my $server = shift;
my $raw_data = shift;
# Set the default indexing modes
+ my $search_engine = C4::Context->preference("SearchEngine");
+ if ($search_engine eq 'Elasticsearch') {
+ return ref $raw_data eq 'MARC::Record' ? $raw_data : MARC::Record->new_from_xml( $raw_data, 'UTF-8' );
+ }
my $index_mode = ( $server eq 'biblioserver' )
? C4::Context->config('zebra_bib_index_mode') // 'dom'
: C4::Context->config('zebra_auth_index_mode') // 'dom';