use C4::Branch;
use C4::Reserves; # CheckReserves
use C4::Debug;
-use C4::Items;
use C4::Charset;
use YAML;
use URI::Escape;
use Business::ISBN;
+use MARC::Record;
+use MARC::Field;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG);
# set the version for version checking
BEGIN {
- $VERSION = 3.01;
+ $VERSION = 3.07.00.049;
$DEBUG = ($ENV{DEBUG}) ? 1 : 0;
}
. $@->code() . ") "
. $@->addinfo() . " "
. $@->diagset();
- warn $error;
+ warn $error." for query: $query";
return ( $error, undef, undef );
}
}
( 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;
# avoid first line
my $tag_num = substr($tag, 0, 3);
my $letters = substr($tag, 3);
- my $field_pattern = '\n' . $tag_num . ' ([^\n]+)';
+ 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 );
|| ( $facets_info->{$link_value}->{'expanded'} ) )
{
- # Sanitize the link value ), ( will cause errors with CCL,
+ # 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;
+ $facet_link_value =~ s/[()!?¡¿؟]/ /g;
# fix the length that will display in the label,
my $facet_label_value = $one_facet;
$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_title_value => $one_facet,
facet_link_value => $facet_link_value,
type_link_value => $link_value,
- };
+ } if ( $facet_label_value );
}
}
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
# FIXME: the locale should be set based on the user's language and/or search choice
#warn "$lang";
+ # Make sure we only use the first two letters from the language code
+ $lang = lc(substr($lang, 0, 2));
+ # The language codes for the two variants of Norwegian will now be "nb" and "nn",
+ # none of which Lingua::Stem::Snowball can use, so we need to "translate" them
+ if ($lang eq 'nb' || $lang eq 'nn') {
+ $lang = 'no';
+ }
my $stemmer = Lingua::Stem::Snowball->new( lang => $lang,
encoding => "UTF-8" );
$weighted_query .=
"Title-cover,ext,r1=\"$operand\""; # exact title-cover
$weighted_query .= " or ti,ext,r2=\"$operand\""; # exact title
- $weighted_query .= " or ti,phr,r3=\"$operand\""; # phrase title
+ $weighted_query .= " or Title-cover,phr,r3=\"$operand\""; # phrase title
#$weighted_query .= " or any,ext,r4=$operand"; # exact any
#$weighted_query .=" or kw,wrdl,r5=\"$operand\""; # word list any
$weighted_query .= " or wrdl,fuzzy,r8=\"$operand\""
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 ( $index, $term ) = @_;
+
+ 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 = ( "(su=\"$term\")");
+ my ($error, $results, $total_hits) = SimpleSearch( "Heading,wrdl=$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, '(su="' . $reference->as_string('abcdefghijlmnopqrstuvxyz') . '")' if (($codes && $codes eq $wantedcodes) || !$wantedcodes);
+ }
+ }
+ }
+ return join(' or ', @subqueries);
+}
+
+=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;
+
+# TODO: once we are using QueryParser, all this special case code for
+# exploded search indexes will be replaced by a callback to
+# _handle_exploding_index
+ if ( $query =~ m/^(.*)\b(su-br|su-na|su-rl)[:=](\w.*)$/ ) {
+ $query = $1;
+ $index = $2;
+ $term = $3;
+ } else {
+ $query = '';
+ for ( my $i = 0 ; $i <= @operands ; $i++ ) {
+ if ($operands[$i] && $indexes[$i] =~ m/(su-br|su-na|su-rl)/) {
+ $index = $indexes[$i];
+ $term = $operands[$i];
+ } elsif ($operands[$i]) {
+ $query .= $operators[$i] eq 'or' ? ' or ' : ' and ' if ($query);
+ $query .= "($indexes[$i]:$operands[$i])";
+ }
+ }
+ }
+
+ if ($index) {
+ my $queryPart = _handle_exploding_index($index, $term);
+ if ($queryPart) {
+ $query .= "($queryPart)";
+ }
+ $operators = ();
+ $operands[0] = "ccl=$query";
+ }
+
+ return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang);
+}
+
=head2 buildQuery
( $error, $query,
warn "---------\nEnter buildQuery\n---------" if $DEBUG;
+ ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang);
+
# dereference
my @operators = $operators ? @$operators : ();
my @indexes = $indexes ? @$indexes : ();
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' );
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
my $dbh = C4::Context->dbh;
my @newresults;
+ require C4::Items;
+
$search_context = 'opac' if !$search_context || $search_context ne 'intranet';
my ($is_opac, $hidelostitems);
if ($search_context eq 'opac') {
}
#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");
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) {
- my @hi = GetHiddenItemnumbers($item);
- $item->{'hideatopac'} = @hi;
- push @hiddenitems, @hi;
+ # hidden because lost
+ if ($hidelostitems && $item->{itemlost}) {
+ $hideatopac_count++;
+ next;
+ }
+ # hidden based on OpacHiddenItems syspref
+ my @hi = C4::Items::GetHiddenItemnumbers($item);
+ 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++;
}
if ( $item->{wthdrawn}
|| $item->{itemlost}
|| $item->{damaged}
- || $item->{notforloan} > 0
- || $item->{hideatopac}
+ || $item->{notforloan}
|| $reservestatus eq 'Waiting'
|| ($transfertwhen ne ''))
{
$wthdrawn_count++ if $item->{wthdrawn};
$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;
+ if ( !$item->{itemlost} ) {
+ if ( !$item->{wthdrawn} ){
+ if ( $item->{damaged} ){
+ if ( C4::Context->preference('AllowHoldsOnDamagedItems') ){
+ # can place a hold on a damaged item if AllowHoldsOnDamagedItems is true
+ if ( ( !$item->{notforloan} || $item->{notforloan} < 0 ) ){
+ # item is either for loan or has notforloan < 0
+ $can_place_holds = 1;
+ }
+ }
+ } elsif ( $item->{notforloan} < 0 ) {
+ # item is not damaged and notforloan is < 0
+ $can_place_holds = 1;
+ }
+ }
}
-
+
$other_count++;
my $key = $prefix . $item->{status};
- foreach (qw(wthdrawn itemlost damaged branchname itemcallnumber hideatopac)) {
+ foreach (qw(wthdrawn itemlost damaged branchname itemcallnumber)) {
$other_items->{$key}->{$_} = $item->{$_};
}
$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;
+ $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};
$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, 'Results',
- $search_context, 1, \@hiddenitems);
+ $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
}
$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");