# Suite 330, Boston, MA 02111-1307 USA
use strict;
-# use warnings; # FIXME
+#use warnings; FIXME - Bug 2505
require Exporter;
use C4::Context;
use C4::Biblio; # GetMarcFromKohaField, GetBiblioData
use C4::Search::PazPar2;
use XML::Simple;
use C4::Dates qw(format_date);
+use C4::Members qw(GetHideLostItemsPreference);
use C4::XSLT;
use C4::Branch;
use C4::Reserves; # CheckReserves
use C4::Debug;
-use YAML;
use URI::Escape;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG);
my $facets_counter = ();
my $facets_info = ();
my $facets = getFacets();
+ my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets')||20;
my @facets_loop; # stores the ref to array of hashes for template facets loop
$sort_by .= "1=9003 >i ";
}
elsif ( $sort eq "call_number_asc" ) {
- $sort_by .= "1=20 <i ";
+ $sort_by .= "1=8007 <i ";
}
elsif ( $sort eq "call_number_dsc" ) {
- $sort_by .= "1=20 >i ";
+ $sort_by .= "1=8007 >i ";
}
elsif ( $sort eq "pubdate_asc" ) {
$sort_by .= "1=31 <i ";
warn "Ignoring unrecognized sort '$sort' requested" if $sort_by;
}
}
- if ($sort_by) {
+ if ($sort_by && !$scan) {
if ( $results[$i]->sort( "yaz", $sort_by ) < 0 ) {
warn "WARNING sort $sort_by failed";
}
for ( my $j = $offset ; $j < $times ; $j++ ) {
my $records_hash;
my $record;
- my $facet_record;
## Check if it's an index scan
if ($scan) {
# warn "RECORD $j:".$record;
$results_hash->{'RECORDS'}[$j] = $record;
-
- # Fill the facets while we're looping, but only for the biblioserver
- $facet_record = MARC::Record->new_from_usmarc($record)
- if $servers[ $i - 1 ] =~ /biblioserver/;
-
- #warn $servers[$i-1]."\n".$record; #.$facet_record->title();
- if ($facet_record) {
- for ( my $k = 0 ; $k <= @$facets ; $k++ ) {
- ($facets->[$k]) or next;
- my @fields = map {$facet_record->field($_)} @{$facets->[$k]->{'tags'}} ;
- for my $field (@fields) {
- my @subfields = $field->subfields();
- for my $subfield (@subfields) {
- my ( $code, $data ) = @$subfield;
- ($code eq $facets->[$k]->{'subfield'}) or next;
- $facets_counter->{ $facets->[$k]->{'link_value'} }->{$data}++;
- }
- }
- $facets_info->{ $facets->[$k]->{'link_value'} }->{'label_value'} =
- $facets->[$k]->{'label_value'};
- $facets_info->{ $facets->[$k]->{'link_value'} }->{'expanded'} =
- $facets->[$k]->{'expanded'};
- }
- }
}
+
}
$results_hashref->{ $servers[ $i - 1 ] } = $results_hash;
+
+ # 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 $k = 0 ; $k <= @$facets ; $k++ ) {
+ ($facets->[$k]) or next;
+ my @fcodes = @{$facets->[$k]->{'tags'}};
+ my $sfcode = $facets->[$k]->{'subfield'};
+
+ for ( my $j = 0 ; $j < $jmax ; $j++ ) {
+ my $render_record = $results[ $i - 1 ]->record($j)->render();
+ my @used_datas = ();
+
+ foreach my $fcode (@fcodes) {
+
+ # avoid first line
+ my $field_pattern = '\n'.$fcode.' ([^\n]+)';
+ my @field_tokens = ( $render_record =~ /$field_pattern/g ) ;
+
+ foreach my $field_token (@field_tokens) {
+ my $subfield_pattern = '\$'.$sfcode.' ([^\$]+)';
+ my @subfield_values = ( $field_token =~ /$subfield_pattern/g );
+
+ foreach my $subfield_value (@subfield_values) {
+
+ my $data = $subfield_value;
+ $data =~ s/^\s+//; # trim left
+ $data =~ s/\s+$//; # trim right
+
+ unless ( $data ~~ @used_datas ) {
+ $facets_counter->{ $facets->[$k]->{'link_value'} }->{$data}++;
+ push @used_datas, $data;
+ }
+ } # subfields
+ } # fields
+ } # field codes
+ } # records
+
+ $facets_info->{ $facets->[$k]->{'link_value'} }->{'label_value'} = $facets->[$k]->{'label_value'};
+ $facets_info->{ $facets->[$k]->{'link_value'} }->{'expanded'} = $facets->[$k]->{'expanded'};
+ } # facets
+ }
+ # End PROGILONE
}
# warn "connection ", $i-1, ": $size hits";
# 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, 20 ) . "..."
- unless length($facet_label_value) <= 20;
+ 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/ ) {
#
foreach ( keys %{ C4::Context->stopwords } ) {
next if ( $_ =~ /(and|or|not)/ ); # don't remove operators
- $debug && warn "$_ Dump($operand)";
if ( my ($matched) = ($operand =~
/([^\X\p{isAlnum}]\Q$_\E[^\X\p{isAlnum}]|[^\X\p{isAlnum}]\Q$_\E$|^\Q$_\E[^\X\p{isAlnum}])/gi))
{
'id-other',
'Illustration-code',
'ISBN',
+ 'isbn',
'ISSN',
+ 'issn',
'itemtype',
'kw',
'Koha-Auth-Number',
'mc-itemtype',
'mc-rtype',
'mus',
+ 'name',
'Name-geographic',
'Name-geographic-heading',
'Name-geographic-see',
'Name-geographic-seealso',
'nb',
'Note',
+ 'notes',
'ns',
'nt',
'pb',
'popularity',
'pubdate',
'Publisher',
+ 'Record-control-number',
+ 'rcn',
'Record-type',
'rtype',
'se',
'reserves',
'restricted',
'stack',
+ 'stocknumber',
+ 'inv',
'uri',
'withdrawn',
# for handling ccl, cql, pqf queries in diagnostic mode, skip the rest of the steps
# DIAGNOSTIC ONLY!!
if ( $query =~ /^ccl=/ ) {
- return ( undef, $', $', "q=ccl=$'", $', '', '', '', '', 'ccl' );
+ 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) {
+ $q .= ' and '.join(' and ', @limits);
+ }
+ return ( undef, $q, $q, "q=ccl=$q", $q, '', '', '', '', 'ccl' );
}
if ( $query =~ /^cql=/ ) {
return ( undef, $', $', "q=cql=$'", $', '', '', '', '', 'cql' );
# COMBINE OPERANDS, INDEXES AND OPERATORS
if ( $operands[$i] ) {
+ $operands[$i]=~s/^\s+//;
# A flag to determine whether or not to add the index to the query
my $indexes_set;
# If the user is sophisticated enough to specify an index, turn off field weighting, stemming, and stopword handling
- if ( $operands[$i] =~ /(:|=)/ || $scan ) {
+ if ( $operands[$i] =~ /\w(:|=)/ || $scan ) {
$weight_fields = 0;
$stemming = 0;
$remove_stopwords = 0;
+ } else {
+ $operands[$i] =~ s/\?/{?}/g; # need to escape question marks
}
my $operand = $operands[$i];
my $index = $indexes[$i];
}
# Set default structure attribute (word list)
- my $struct_attr;
+ my $struct_attr = q{};
unless ( $indexes_set || !$index || $index =~ /(st-|phr|ext|wrdl)/ ) {
$struct_attr = ",wrdl";
}
# Some helpful index variants
- my $index_plus = $index . $struct_attr . ":" if $index;
- my $index_plus_comma = $index . $struct_attr . "," if $index;
+ my $index_plus = $index . $struct_attr . ':';
+ my $index_plus_comma = $index . $struct_attr . ',';
# Remove Stopwords
if ($remove_stopwords) {
my $group_OR_limits;
my $availability_limit;
foreach my $this_limit (@limits) {
-# if ( $this_limit =~ /available/ ) {
+ if ( $this_limit =~ /available/ ) {
#
## 'available' is defined as (items.onloan is NULL) and (items.itemlost = 0)
## 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='')) )";
-# $limit_cgi .= "&limit=available";
-# $limit_desc .= "";
-# }
-#
+ $availability_limit .=
+"( ( allrecords,AlwaysMatches='' not onloan,AlwaysMatches='') and (lost,st-numeric=0) )"; #or ( allrecords,AlwaysMatches='' not lost,AlwaysMatches='')) )";
+ $limit_cgi .= "&limit=available";
+ $limit_desc .= "";
+ }
+
# group_OR_limits, prefixed by mc-
# OR every member of the group
-# elsif ( $this_limit =~ /mc/ ) {
- if ( $this_limit =~ /mc/ ) {
+ elsif ( $this_limit =~ /mc/ ) {
+
+ if ( $this_limit =~ /mc-ccode:/ ) {
+ # in case the mc-ccode value has complicating chars like ()'s inside it we wrap in quotes
+ $this_limit =~ tr/"//d;
+ my ($k,$v) = split(/:/, $this_limit,2);
+ $this_limit = $k.":\"".$v."\"";
+ }
+
$group_OR_limits .= " or " if $group_OR_limits;
$limit_desc .= " or " if $group_OR_limits;
$group_OR_limits .= "$this_limit";
# if user wants to do ccl or cql, start the query with that
# $query =~ s/:/=/g;
$query =~ s/(?<=(ti|au|pb|su|an|kw|mc)):/=/g;
- $query =~ s/(?<=rtrn):/=/g;
+ $query =~ s/(?<=(wrdl)):/=/g;
+ $query =~ s/(?<=(trn|phr)):/=/g;
$limit =~ s/:/=/g;
for ( $query, $query_desc, $limit, $limit_desc ) {
- s/ / /g; # remove extra spaces
+ s/ +/ /g; # remove extra spaces
s/^ //g; # remove any beginning spaces
s/ $//g; # remove any ending spaces
s/==/=/g; # remove double == from query
=head2 searchResults
+ my @search_results = searchResults($search_context, $searchdesc, $hits,
+ $results_per_page, $offset, $scan,
+ @marcresults, $hidelostitems);
+
Format results in a form suitable for passing to the template
=cut
# IMO this subroutine is pretty messy still -- it's responsible for
# building the HTML output for the template
sub searchResults {
- my ( $searchdesc, $hits, $results_per_page, $offset, $scan, @marcresults, $hidelostitems ) = @_;
+ my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, @marcresults, $hidelostitems ) = @_;
my $dbh = C4::Context->dbh;
my @newresults;
+ $search_context = 'opac' unless $search_context eq 'opac' or $search_context eq 'intranet';
+
#Build branchnames hash
#find branchname
#get branch information.....
# 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] );
- if ($bibliotag<10){
- $fw = GetFrameworkCode($marcrecord->field($bibliotag)->data);
- }else{
- $fw = GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf));
- }
-
+ $fw = $scan
+ ? undef
+ : $bibliotag < 10
+ ? GetFrameworkCode($marcrecord->field($bibliotag)->data)
+ : GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf));
my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, $fw );
$oldbiblio->{subtitle} = GetRecordValue('subtitle', $marcrecord, $fw);
$oldbiblio->{result_number} = $i + 1;
# add imageurl to itemtype if there is one
- $oldbiblio->{imageurl} = getitemtypeimagelocation( 'opac', $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} );
+ $oldbiblio->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} );
- $oldbiblio->{'authorised_value_images'} = C4::Items::get_authorised_value_images( C4::Biblio::get_biblio_authorised_values( $oldbiblio->{'biblionumber'}, $marcrecord ) );
+ $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);
my $prefix = $item->{$hbranch} . '--' . $item->{location} . $item->{itype} . $item->{itemcallnumber};
# For each grouping of items (onloan, available, unavailable), we build a key to store relevant info about that item
- if ( $item->{onloan} ) {
+ my $userenv = C4::Context->userenv;
+ if ( $item->{onloan} && !(C4::Members::GetHideLostItemsPreference($userenv->{'number'}) && $item->{itemlost}) ) {
$onloan_count++;
my $key = $prefix . $item->{onloan} . $item->{barcode};
$onloan_items->{$key}->{due_date} = format_date($item->{onloan});
$onloan_items->{$key}->{branchname} = $item->{branchname};
$onloan_items->{$key}->{location} = $shelflocations->{ $item->{location} };
$onloan_items->{$key}->{itemcallnumber} = $item->{itemcallnumber};
- $onloan_items->{$key}->{imageurl} = getitemtypeimagelocation( 'opac', $itemtypes{ $item->{itype} }->{imageurl} );
+ $onloan_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
# if something's checked out and lost, mark it as 'long overdue'
if ( $item->{itemlost} ) {
$onloan_items->{$prefix}->{longoverdue}++;
if ( $item->{wthdrawn}
|| $item->{itemlost}
|| $item->{damaged}
- || $item->{notforloan}
+ || $item->{notforloan} > 0
|| $reservestatus eq 'Waiting'
|| ($transfertwhen ne ''))
{
$other_items->{$key}->{notforloan} = GetAuthorisedValueDesc('','',$item->{notforloan},'','',$notforloan_authorised_value) if $notforloan_authorised_value;
$other_items->{$key}->{count}++ if $item->{$hbranch};
$other_items->{$key}->{location} = $shelflocations->{ $item->{location} };
- $other_items->{$key}->{imageurl} = getitemtypeimagelocation( 'opac', $itemtypes{ $item->{itype} }->{imageurl} );
+ $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
}
# item is available
else {
$available_items->{$prefix}->{$_} = $item->{$_};
}
$available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} };
- $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( 'opac', $itemtypes{ $item->{itype} }->{imageurl} );
+ $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
}
}
} # notforloan, item level and biblioitem level
use C4::Charset;
SetUTF8Flag($marcrecord);
$debug && warn $marcrecord->as_formatted;
- if (C4::Context->preference("XSLTResultsDisplay") && !$scan) {
- $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display(
- $oldbiblio->{biblionumber}, $marcrecord, 'Results' );
+ if (!$scan && $search_context eq 'opac' && C4::Context->preference("OPACXSLTResultsDisplay")) {
+ # FIXME note that XSLTResultsDisplay (use of XSLT to format staff interface bib search results)
+ # is not implemented yet
+ $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, 'Results',
+ $search_context, 1);
+ # the last parameter tells Koha to clean up the problematic ampersand entities that Zebra outputs
+
}
- # last check for norequest : if itemtype is notforloan, it can't be reserved either, whatever the items
- $can_place_holds = 0
- if $itemtypes{ $oldbiblio->{itemtype} }->{notforloan};
+ # 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}) {
+ $can_place_holds = 0;
+ }
+ }
$oldbiblio->{norequests} = 1 unless $can_place_holds;
$oldbiblio->{itemsplural} = 1 if $items_count > 1;
$oldbiblio->{items_count} = $items_count;
$oldbiblio->{orderedcount} = $ordered_count;
$oldbiblio->{isbn} =~
s/-//g; # deleting - in isbn to enable amazon content
+
+ if (C4::Context->preference("AlternateHoldingsField") && $items_count == 0) {
+ my $fieldspec = C4::Context->preference("AlternateHoldingsField");
+ my $subfields = substr $fieldspec, 3;
+ my $holdingsep = C4::Context->preference("AlternateHoldingsSeparator") || ' ';
+ my @alternateholdingsinfo = ();
+ my @holdingsfields = $marcrecord->field(substr $fieldspec, 0, 3);
+ my $alternateholdingscount = 0;
+
+ for my $field (@holdingsfields) {
+ my %holding = ( holding => '' );
+ my $havesubfield = 0;
+ for my $subfield ($field->subfields()) {
+ if ((index $subfields, $$subfield[0]) >= 0) {
+ $holding{'holding'} .= $holdingsep if (length $holding{'holding'} > 0);
+ $holding{'holding'} .= $$subfield[1];
+ $havesubfield++;
+ }
+ }
+ if ($havesubfield) {
+ push(@alternateholdingsinfo, \%holding);
+ $alternateholdingscount++;
+ }
+ }
+
+ $oldbiblio->{'ALTERNATEHOLDINGS'} = \@alternateholdingsinfo;
+ $oldbiblio->{'alternateholdings_count'} = $alternateholdingscount;
+ }
+
push( @newresults, $oldbiblio )
if(not $hidelostitems
or (($items_count > $itemlost_count )
# depending of operand, intersect, union or exclude both lists
# to get a result list
if ( $operator eq ' and ' ) {
- warn "NZAND";
return NZoperatorAND($leftresult,$rightresult);
}
elsif ( $operator eq ' or ' ) {
#There are no results, build authority record, add it to Authorities, get authid and add it to 9
###NOTICE : This is only valid if a subfield is linked to one and only one authtypecode
###NOTICE : This can be a problem. We should also look into other types and rejected forms.
- my $authtypedata=C4::AuthoritiesMarc->GetAuthType($data->{authtypecode});
+ my $authtypedata=C4::AuthoritiesMarc::GetAuthType($data->{authtypecode});
next unless $authtypedata;
my $marcrecordauth=MARC::Record->new();
my $authfield=MARC::Field->new($authtypedata->{auth_tag_to_report},'','',"a"=>"".$field->subfield('a'));
=head1 AUTHOR
-Koha Developement team <info@koha.org>
+Koha Development Team <http://koha-community.org/>
=cut