X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FSearch.pm;h=7c20780e5ff991332a959bbd6453b90309915ea7;hb=6eeb5180422d9439afb9e783aae70f7c1beb27e7;hp=56468b5098ca6f94e5a13a23374bfbdc81b65669;hpb=df0a6a71d7301e4e2e346bfe50e00a8e135da33e;p=srvgit diff --git a/C4/Search.pm b/C4/Search.pm index 56468b5098..7c20780e5f 100644 --- a/C4/Search.pm +++ b/C4/Search.pm @@ -30,17 +30,18 @@ use C4::XSLT; 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; } @@ -249,7 +250,7 @@ sub SimpleSearch { . $@->code() . ") " . $@->addinfo() . " " . $@->diagset(); - warn $error; + warn $error." for query: $query"; return ( $error, undef, undef ); } } @@ -288,7 +289,7 @@ sub SimpleSearch { ( 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 ); @@ -303,7 +304,7 @@ sub getRecords { 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; @@ -478,7 +479,8 @@ sub getRecords { # 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 ); @@ -556,6 +558,22 @@ sub getRecords { $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, { @@ -564,7 +582,7 @@ sub getRecords { facet_title_value => $one_facet, facet_link_value => $facet_link_value, type_link_value => $link_value, - }; + } if ( $facet_label_value ); } } @@ -722,7 +740,7 @@ sub _detect_truncation { 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 @@ -733,6 +751,13 @@ sub _build_stemmed_operand { # 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" ); @@ -765,7 +790,7 @@ sub _build_weighted_query { $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\"" @@ -1011,6 +1036,104 @@ sub getIndexes{ return \@indexes; } +=head2 _handle_exploding_index + + my $query = _handle_exploding_index($index, $term) + +Callback routine to generate the search for "exploding" indexes (i.e. +those indexes which are turned into multiple or-connected searches based +on authority data). + +=cut + +sub _handle_exploding_index { + my ( $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, @@ -1032,6 +1155,8 @@ sub buildQuery { 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 : (); @@ -1083,7 +1208,8 @@ sub buildQuery { 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' ); @@ -1300,6 +1426,7 @@ sub buildQuery { 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) @@ -1398,7 +1525,7 @@ sub buildQuery { 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 @@ -1411,6 +1538,8 @@ sub searchResults { 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') { @@ -1450,12 +1579,7 @@ sub searchResults { } #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"); @@ -1610,9 +1734,9 @@ sub searchResults { 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; @@ -1622,11 +1746,20 @@ sub searchResults { } $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'; @@ -1705,14 +1838,12 @@ sub searchResults { || $item->{itemlost} || $item->{damaged} || $item->{notforloan} > 0 - || $item->{hideatopac} || $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}; @@ -1728,12 +1859,12 @@ sub searchResults { $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}; @@ -1744,7 +1875,7 @@ sub searchResults { $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} }; @@ -1752,10 +1883,12 @@ sub searchResults { } } } # 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; @@ -1773,7 +1906,7 @@ sub searchResults { # 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); @@ -1804,8 +1937,6 @@ sub searchResults { $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");