3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 2 of the License, or (at your option) any later
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along with
15 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
16 # Suite 330, Boston, MA 02111-1307 USA
21 use C4::Biblio; # GetMarcFromKohaField
22 use C4::Koha; # getFacets
25 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
27 # set the version for version checking
28 $VERSION = do { my @v = '$Revision$' =~ /\d+/g;
29 shift(@v) . "." . join( "_", map { sprintf "%03d", $_ } @v );
34 C4::Search - Functions for searching the Koha catalog.
38 see opac/opac-search.pl or catalogue/search.pl for example of usage
42 This module provides the searching facilities for the Koha into a zebra catalog.
58 # make all your functions, whether exported or not;
60 =head2 findseealso($dbh,$fields);
62 C<$dbh> is a link to the DB handler.
65 my $dbh =C4::Context->dbh;
67 C<$fields> is a reference to the fields array
69 This function modify the @$fields array and add related fields to search on.
74 my ( $dbh, $fields ) = @_;
75 my $tagslib = GetMarcStructure( $dbh, 1 );
76 for ( my $i = 0 ; $i <= $#{$fields} ; $i++ ) {
77 my ($tag) = substr( @$fields[$i], 1, 3 );
78 my ($subfield) = substr( @$fields[$i], 4, 1 );
79 @$fields[$i] .= ',' . $tagslib->{$tag}->{$subfield}->{seealso}
80 if ( $tagslib->{$tag}->{$subfield}->{seealso} );
86 ($biblionumber,$biblionumber,$title) = FindDuplicate($record);
93 my $dbh = C4::Context->dbh;
94 my $result = TransformMarcToKoha( $dbh, $record, '' );
99 my ( $biblionumber, $title );
101 # search duplicate on ISBN, easy and fast..
102 #$search->{'avoidquerylog'}=1;
103 if ( $result->{isbn} ) {
104 $query = "isbn=$result->{isbn}";
107 $result->{title} =~ s /\\//g;
108 $result->{title} =~ s /\"//g;
109 $result->{title} =~ s /\(//g;
110 $result->{title} =~ s /\)//g;
111 $query = "ti,ext=$result->{title}";
113 my ($possible_duplicate_record) =
114 C4::Biblio::getRecord( "biblioserver", $query, "usmarc" ); # FIXME :: hardcoded !
115 if ($possible_duplicate_record) {
117 MARC::Record->new_from_usmarc($possible_duplicate_record);
118 my $result = TransformMarcToKoha( $dbh, $marcrecord, '' );
120 # FIXME :: why 2 $biblionumber ?
121 return $result->{'biblionumber'}, $result->{'biblionumber'},
129 ($error,$results) = SimpleSearch($query,@servers);
131 this function performs a simple search on the catalog using zoom.
137 * $query could be a simple keyword or a complete CCL query wich is depending on your ccl file.
138 * @servers is optionnal. default one is read on koha.xml
141 * $error is a string which containt the description error if there is one. Else it's empty.
142 * \@results is an array of marc record.
144 =item C<usage in the script:>
148 my ($error, $marcresults) = SimpleSearch($query);
150 if (defined $error) {
151 $template->param(query_error => $error);
152 warn "error: ".$error;
153 output_html_with_http_headers $input, $cookie, $template->output;
157 my $hits = scalar @$marcresults;
160 for(my $i=0;$i<$hits;$i++) {
162 my $marcrecord = MARC::File::USMARC::decode($marcresults->[$i]);
163 my $biblio = TransformMarcToKoha(C4::Context->dbh,$marcrecord,'');
165 #build the hash for the template.
166 $resultsloop{highlight} = ($i % 2)?(1):(0);
167 $resultsloop{title} = $biblio->{'title'};
168 $resultsloop{subtitle} = $biblio->{'subtitle'};
169 $resultsloop{biblionumber} = $biblio->{'biblionumber'};
170 $resultsloop{author} = $biblio->{'author'};
171 $resultsloop{publishercode} = $biblio->{'publishercode'};
172 $resultsloop{publicationyear} = $biblio->{'publicationyear'};
174 push @results, \%resultsloop;
176 $template->param(result=>\@results);
186 return ( "No query entered", undef ) unless $query;
188 #@servers = (C4::Context->config("biblioserver")) unless @servers;
190 ("biblioserver") unless @servers
191 ; # FIXME hardcoded value. See catalog/search.pl & opac-search.pl too.
194 for ( my $i = 0 ; $i < @servers ; $i++ ) {
195 $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
198 ->search( new ZOOM::Query::CCL2RPN( $query, $zconns[$i] ) );
200 # getting error message if one occured.
202 $zconns[$i]->errmsg() . " ("
203 . $zconns[$i]->errcode() . ") "
204 . $zconns[$i]->addinfo() . " "
205 . $zconns[$i]->diagset();
207 return ( $error, undef ) if $zconns[$i]->errcode();
211 while ( ( my $i = ZOOM::event( \@zconns ) ) != 0 ) {
212 $ev = $zconns[ $i - 1 ]->last_event();
213 if ( $ev == ZOOM::Event::ZEND ) {
214 $hits = $tmpresults[ $i - 1 ]->size();
217 for ( my $j = 0 ; $j < $hits ; $j++ ) {
218 my $record = $tmpresults[ $i - 1 ]->record($j)->raw();
219 push @results, $record;
223 return ( undef, \@results );
226 # performs the search
229 $koha_query, $federated_query, $sort_by_ref,
230 $servers_ref, $results_per_page, $offset,
231 $expanded_facet, $branches, $query_type,
235 my @servers = @$servers_ref;
236 my @sort_by = @$sort_by_ref;
238 # create the zoom connection and query object
242 my $results_hashref = ();
245 my $facets_counter = ();
246 my $facets_info = ();
247 my $facets = getFacets();
249 #### INITIALIZE SOME VARS USED CREATE THE FACETED RESULTS
250 my @facets_loop; # stores the ref to array of hashes for template
251 for ( my $i = 0 ; $i < @servers ; $i++ ) {
252 $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
254 # perform the search, create the results objects
255 # if this is a local search, use the $koha-query, if it's a federated one, use the federated-query
257 if ( $servers[$i] =~ /biblioserver/ ) {
258 $query_to_use = $koha_query;
261 $query_to_use = $federated_query;
264 # warn "HERE : $query_type => $query_to_use";
265 # check if we've got a query_type defined
269 if ( $query_type =~ /^ccl/ ) {
271 s/\:/\=/g; # change : to = last minute (FIXME)
273 # warn "CCL : $query_to_use";
276 new ZOOM::Query::CCL2RPN( $query_to_use, $zconns[$i] )
279 elsif ( $query_type =~ /^cql/ ) {
281 # warn "CQL : $query_to_use";
284 new ZOOM::Query::CQL( $query_to_use, $zconns[$i] ) );
286 elsif ( $query_type =~ /^pqf/ ) {
288 # warn "PQF : $query_to_use";
291 new ZOOM::Query::PQF( $query_to_use, $zconns[$i] ) );
297 # warn "preparing to scan";
300 new ZOOM::Query::CCL2RPN( $query_to_use, $zconns[$i] )
305 # warn "LAST : $query_to_use";
308 new ZOOM::Query::CCL2RPN( $query_to_use, $zconns[$i] )
314 warn "prob with query toto $query_to_use " . $@;
317 # concatenate the sort_by limits and pass them to the results object
319 foreach my $sort (@sort_by) {
320 $sort_by .= $sort . " "; # used to be $sort,
322 $results[$i]->sort( "yaz", $sort_by ) if $sort_by;
324 while ( ( my $i = ZOOM::event( \@zconns ) ) != 0 ) {
325 my $ev = $zconns[ $i - 1 ]->last_event();
326 if ( $ev == ZOOM::Event::ZEND ) {
327 my $size = $results[ $i - 1 ]->size();
330 #$results_hash->{'server'} = $servers[$i-1];
331 # loop through the results
332 $results_hash->{'hits'} = $size;
334 if ( $offset + $results_per_page <= $size ) {
335 $times = $offset + $results_per_page;
340 for ( my $j = $offset ; $j < $times ; $j++ )
341 { #(($offset+$count<=$size) ? ($offset+$count):$size) ; $j++){
345 ## This is just an index scan
347 my ( $term, $occ ) = $results[ $i - 1 ]->term($j);
349 # here we create a minimal MARC record and hand it off to the
350 # template just like a normal result ... perhaps not ideal, but
352 my $tmprecord = MARC::Record->new();
353 $tmprecord->encoding('UTF-8');
356 # srote the minimal record in author/title (depending on MARC flavour)
357 if ( C4::Context->preference("marcflavour") eq
360 $tmptitle = MARC::Field->new(
367 $tmptitle = MARC::Field->new(
373 $tmprecord->append_fields($tmptitle);
374 $results_hash->{'RECORDS'}[$j] =
375 $tmprecord->as_usmarc();
378 $record = $results[ $i - 1 ]->record($j)->raw();
380 #warn "RECORD $j:".$record;
381 $results_hash->{'RECORDS'}[$j] =
382 $record; # making a reference to a hash
383 # Fill the facets while we're looping
384 $facet_record = MARC::Record->new_from_usmarc($record);
386 #warn $servers[$i-1].$facet_record->title();
387 for ( my $k = 0 ; $k <= @$facets ; $k++ ) {
388 if ( $facets->[$k] ) {
390 for my $tag ( @{ $facets->[$k]->{'tags'} } ) {
391 push @fields, $facet_record->field($tag);
393 for my $field (@fields) {
394 my @subfields = $field->subfields();
395 for my $subfield (@subfields) {
396 my ( $code, $data ) = @$subfield;
398 $facets->[$k]->{'subfield'} )
400 $facets_counter->{ $facets->[$k]
401 ->{'link_value'} }->{$data}++;
405 $facets_info->{ $facets->[$k]->{'link_value'} }
407 $facets->[$k]->{'label_value'};
408 $facets_info->{ $facets->[$k]->{'link_value'} }
409 ->{'expanded'} = $facets->[$k]->{'expanded'};
414 $results_hashref->{ $servers[ $i - 1 ] } = $results_hash;
417 #print "connection ", $i-1, ": $size hits";
418 #print $results[$i-1]->record(0)->render() if $size > 0;
421 sort { $facets_counter->{$b} <=> $facets_counter->{$a} }
422 keys %$facets_counter
426 my $number_of_facets;
427 my @this_facets_array;
430 $facets_counter->{$link_value}
431 ->{$b} <=> $facets_counter->{$link_value}->{$a}
432 } keys %{ $facets_counter->{$link_value} }
436 if ( ( $number_of_facets < 6 )
437 || ( $expanded_facet eq $link_value )
438 || ( $facets_info->{$link_value}->{'expanded'} ) )
441 # sanitize the link value ), ( will cause errors with CCL
442 my $facet_link_value = $one_facet;
443 $facet_link_value =~ s/(\(|\))/ /g;
445 # fix the length that will display in the label
446 my $facet_label_value = $one_facet;
447 $facet_label_value = substr( $one_facet, 0, 20 ) . "..."
448 unless length($facet_label_value) <= 20;
450 # well, if it's a branch, label by the name, not the code
451 if ( $link_value =~ /branch/ ) {
453 $branches->{$one_facet}->{'branchname'};
456 # but we're down with the whole label being in the link's title
457 my $facet_title_value = $one_facet;
459 push @this_facets_array,
463 $facets_counter->{$link_value}->{$one_facet},
464 facet_label_value => $facet_label_value,
465 facet_title_value => $facet_title_value,
466 facet_link_value => $facet_link_value,
467 type_link_value => $link_value,
472 unless ( $facets_info->{$link_value}->{'expanded'} ) {
474 if ( ( $number_of_facets > 6 )
475 && ( $expanded_facet ne $link_value ) );
480 type_link_value => $link_value,
481 type_id => $link_value . "_id",
483 $facets_info->{$link_value}->{'label_value'},
484 facets => \@this_facets_array,
485 expandable => $expandable,
486 expand => $link_value,
492 return ( undef, $results_hashref, \@facets_loop );
495 # build the query itself
497 my ( $query, $operators, $operands, $indexes, $limits, $sort_by ) = @_;
499 my @operators = @$operators if $operators;
500 my @indexes = @$indexes if $indexes;
501 my @operands = @$operands if $operands;
502 my @limits = @$limits if $limits;
503 my @sort_by = @$sort_by if $sort_by;
505 my $human_search_desc; # a human-readable query
506 my $machine_search_desc; #a machine-readable query
507 # FIXME: the locale should be set based on the syspref
508 my $stemmer = Lingua::Stem->new( -locale => 'EN-US' );
510 # FIXME: these should be stored in the db so the librarian can modify the behavior
511 $stemmer->add_exceptions(
519 # STEP I: determine if this is a form-based / simple query or if it's complex (if complex,
520 # we can't handle field weighting, stemming until a formal query parser is written
521 # I'll work on this soon -- JF
522 #if (!$query) { # form-based
523 # check if this is a known query language query, if it is, return immediately:
524 if ( $query =~ /^ccl=/ ) {
525 return ( undef, $', $', $', 'ccl' );
527 if ( $query =~ /^cql=/ ) {
528 return ( undef, $', $', $', 'cql' );
530 if ( $query =~ /^pqf=/ ) {
531 return ( undef, $', $', $', 'pqf' );
533 if ( $query =~ /(\(|\))/ ) { # sorry, too complex
534 return ( undef, $query, $query, $query, 'ccl' );
537 # form-based queries are limited to non-nested a specific depth, so we can easily
538 # modify the incoming query operands and indexes to do stemming and field weighting
539 # Once we do so, we'll end up with a value in $query, just like if we had an
540 # incoming $query from the user
543 ; # clear it out so we can populate properly with field-weighted stemmed query
545 ; # a flag used to keep track if there was a previous query
546 # if there was, we can apply the current operator
547 for ( my $i = 0 ; $i <= @operands ; $i++ ) {
548 my $operand = $operands[$i];
549 my $index = $indexes[$i];
551 my $stemming = C4::Context->parameters("Stemming") || 0;
552 my $weight_fields = C4::Context->parameters("WeightFields") || 0;
554 if ( $operands[$i] ) {
556 # STEMMING FIXME: need to refine the field weighting so stemmed operands don't disrupt the query ranking
558 my @words = split( / /, $operands[$i] );
559 my $stems = $stemmer->stem(@words);
560 foreach my $stem (@$stems) {
561 $stemmed_operand .= "$stem";
562 $stemmed_operand .= "?"
563 unless ( $stem =~ /(and$|or$|not$)/ )
564 || ( length($stem) < 3 );
565 $stemmed_operand .= " ";
567 #warn "STEM: $stemmed_operand";
570 #$operand = $stemmed_operand;
573 # FIELD WEIGHTING - This is largely experimental stuff. What I'm committing works
574 # pretty well but will work much better when we have an actual query parser
576 if ($weight_fields) {
578 " rk=("; # Specifies that we're applying rank
579 # keyword has different weight properties
580 if ( ( $index =~ /kw/ ) || ( !$index ) )
581 { # FIXME: do I need to add right-truncation in the case of stemming?
582 # a simple way to find out if this query uses an index
583 if ( $operand =~ /(\=|\:)/ ) {
584 $weighted_query .= " $operand";
588 " Title-cover,ext,r1=\"$operand\""
589 ; # index label as exact
591 " or ti,ext,r2=$operand"; # index as exact
592 #$weighted_query .= " or ti,phr,r3=$operand"; # index as phrase
593 #$weighted_query .= " or any,ext,r4=$operand"; # index as exact
595 " or kw,wrdl,r5=$operand"; # index as exact
596 $weighted_query .= " or wrd,fuzzy,r9=$operand";
597 $weighted_query .= " or wrd=$stemmed_operand"
601 elsif ( $index =~ /au/ ) {
603 " $index,ext,r1=$operand"; # index label as exact
604 #$weighted_query .= " or (title-sort-az=0 or $index,startswithnt,st-word,r3=$operand #)";
606 " or $index,phr,r3=$operand"; # index as phrase
607 $weighted_query .= " or $index,rt,wrd,r3=$operand";
609 elsif ( $index =~ /ti/ ) {
611 " Title-cover,ext,r1=$operand"; # index label as exact
612 $weighted_query .= " or Title-series,ext,r2=$operand";
614 #$weighted_query .= " or ti,ext,r2=$operand";
615 #$weighted_query .= " or ti,phr,r3=$operand";
616 #$weighted_query .= " or ti,wrd,r3=$operand";
618 " or (title-sort-az=0 or Title-cover,startswithnt,st-word,r3=$operand #)";
620 " or (title-sort-az=0 or Title-cover,phr,r6=$operand)";
622 #$weighted_query .= " or Title-cover,wrd,r5=$operand";
623 #$weighted_query .= " or ti,ext,r6=$operand";
624 #$weighted_query .= " or ti,startswith,phr,r7=$operand";
625 #$weighted_query .= " or ti,phr,r8=$operand";
626 #$weighted_query .= " or ti,wrd,r9=$operand";
628 #$weighted_query .= " or ti,ext,r2=$operand"; # index as exact
629 #$weighted_query .= " or ti,phr,r3=$operand"; # index as phrase
630 #$weighted_query .= " or any,ext,r4=$operand"; # index as exact
631 #$weighted_query .= " or kw,wrd,r5=$operand"; # index as exact
635 " $index,ext,r1=$operand"; # index label as exact
636 #$weighted_query .= " or $index,ext,r2=$operand"; # index as exact
638 " or $index,phr,r3=$operand"; # index as phrase
639 $weighted_query .= " or $index,rt,wrd,r3=$operand";
641 " or $index,wrd,r5=$operand"
642 ; # index as word right-truncated
643 $weighted_query .= " or $index,wrd,fuzzy,r8=$operand";
645 $weighted_query .= ")"; # close rank specification
646 $operand = $weighted_query;
649 # only add an operator if there is a previous operand
650 if ($previous_operand) {
651 if ( $operators[ $i - 1 ] ) {
652 $query .= " $operators[$i-1] $index: $operand";
654 $human_search_desc .=
655 " $operators[$i-1] $operands[$i]";
658 $human_search_desc .=
659 " $operators[$i-1] $index: $operands[$i]";
663 # the default operator is and
665 $query .= " and $index: $operand";
666 $human_search_desc .= " and $index: $operands[$i]";
671 $query .= " $operand";
672 $human_search_desc .= " $operands[$i]";
675 $query .= " $index: $operand";
676 $human_search_desc .= " $index: $operands[$i]";
678 $previous_operand = 1;
686 my $limit_search_desc;
687 foreach my $limit (@limits) {
689 # FIXME: not quite right yet ... will work on this soon -- JF
690 my $type = $1 if $limit =~ m/([^:]+):([^:]*)/;
691 if ( $limit =~ /available/ ) {
693 " (($query and datedue=0000-00-00) or ($query and datedue=0000-00-00 not lost=1) or ($query and datedue=0000-00-00 not lost=2))";
695 #$limit_search_desc.=" and available";
697 elsif ( ($limit_query) && ( index( $limit_query, $type, 0 ) > 0 ) ) {
698 if ( $limit_query !~ /\(/ ) {
700 substr( $limit_query, 0, index( $limit_query, $type, 0 ) )
702 . substr( $limit_query, index( $limit_query, $type, 0 ) )
706 substr( $limit_search_desc, 0,
707 index( $limit_search_desc, $type, 0 ) )
709 . substr( $limit_search_desc,
710 index( $limit_search_desc, $type, 0 ) )
716 chop $limit_search_desc;
717 $limit_query .= " or $limit )" if $limit;
718 $limit_search_desc .= " or $limit )" if $limit;
721 elsif ( ($limit_query) && ( $limit =~ /mc/ ) ) {
722 $limit_query .= " or $limit" if $limit;
723 $limit_search_desc .= " or $limit" if $limit;
726 # these are treated as AND
727 elsif ($limit_query) {
728 $limit_query .= " or $limit" if $limit;
729 $limit_search_desc .= " or $limit" if $limit;
732 # otherwise, there is nothing but the limit
734 $limit_query .= "$limit" if $limit;
735 $limit_search_desc .= "$limit" if $limit;
739 # if there's also a query, we need to AND the limits to it
740 if ( ($limit_query) && ($query) ) {
741 $limit_query = " and (" . $limit_query . ")";
742 $limit_search_desc = " and ($limit_search_desc)" if $limit_search_desc;
745 $query .= $limit_query;
746 $human_search_desc .= $limit_search_desc;
748 # now normalize the strings
749 $query =~ s/ / /g; # remove extra spaces
750 $query =~ s/^ //g; # remove any beginning spaces
751 $query =~ s/:/=/g; # causes probs for server
752 $query =~ s/==/=/g; # remove double == from query
754 my $federated_query = $human_search_desc;
755 $federated_query =~ s/ / /g;
756 $federated_query =~ s/^ //g;
757 $federated_query =~ s/:/=/g;
758 my $federated_query_opensearch = $federated_query;
760 # my $federated_query_RPN = new ZOOM::Query::CCL2RPN( $query , C4::Context->ZConn('biblioserver'));
762 $human_search_desc =~ s/ / /g;
763 $human_search_desc =~ s/^ //g;
764 my $koha_query = $query;
766 #warn "QUERY:".$koha_query;
767 #warn "SEARCHDESC:".$human_search_desc;
768 #warn "FEDERATED QUERY:".$federated_query;
769 return ( undef, $human_search_desc, $koha_query, $federated_query );
772 # IMO this subroutine is pretty messy still -- it's responsible for
773 # building the HTML output for the template
775 my ( $searchdesc, $hits, $results_per_page, $offset, @marcresults ) = @_;
777 my $dbh = C4::Context->dbh;
781 my $span_terms_hashref;
782 for my $span_term ( split( / /, $searchdesc ) ) {
783 $span_term =~ s/(.*=|\)|\(|\+|\.)//g;
784 $span_terms_hashref->{$span_term}++;
787 #Build brancnames hash
789 #get branch information.....
792 $dbh->prepare("SELECT branchcode,branchname FROM branches")
793 ; # FIXME : use C4::Koha::GetBranches
795 while ( my $bdata = $bsth->fetchrow_hashref ) {
796 $branches{ $bdata->{'branchcode'} } = $bdata->{'branchname'};
800 #find itemtype & itemtype image
803 $dbh->prepare("SELECT itemtype,description,imageurl,summary FROM itemtypes");
805 while ( my $bdata = $bsth->fetchrow_hashref ) {
806 $itemtypes{ $bdata->{'itemtype'} }->{description} =
807 $bdata->{'description'};
808 $itemtypes{ $bdata->{'itemtype'} }->{imageurl} = $bdata->{'imageurl'};
809 $itemtypes{ $bdata->{'itemtype'} }->{summary} = $bdata->{'summary'};
812 #search item field code
815 "select tagfield from marc_subfield_structure where kohafield like 'items.itemnumber'"
818 my ($itemtag) = $sth->fetchrow;
820 ## find column names of items related to MARC
821 my $sth2 = $dbh->prepare("SHOW COLUMNS from items");
823 my %subfieldstosearch;
824 while ( ( my $column ) = $sth2->fetchrow ) {
825 my ( $tagfield, $tagsubfield ) =
826 &GetMarcFromKohaField( $dbh, "items." . $column, "" );
827 $subfieldstosearch{$column} = $tagsubfield;
831 if ( $hits && $offset + $results_per_page <= $hits ) {
832 $times = $offset + $results_per_page;
838 for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) {
840 $marcrecord = MARC::File::USMARC::decode( $marcresults[$i] );
842 my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, '' );
844 # add image url if there is one
845 if ( $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} =~ /^http:/ ) {
846 $oldbiblio->{imageurl} =
847 $itemtypes{ $oldbiblio->{itemtype} }->{imageurl};
848 $oldbiblio->{description} =
849 $itemtypes{ $oldbiblio->{itemtype} }->{description};
852 $oldbiblio->{imageurl} =
853 getitemtypeimagesrc() . "/"
854 . $itemtypes{ $oldbiblio->{itemtype} }->{imageurl}
855 if ( $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} );
856 $oldbiblio->{description} =
857 $itemtypes{ $oldbiblio->{itemtype} }->{description};
860 # build summary if there is one (the summary is defined in itemtypes table
862 if ($itemtypes{ $oldbiblio->{itemtype} }->{summary}) {
863 my $summary = $itemtypes{ $oldbiblio->{itemtype} }->{summary};
864 my @fields = $marcrecord->fields();
865 foreach my $field (@fields) {
866 my $tag = $field->tag();
867 my $tagvalue = $field->as_string();
868 $summary =~ s/\[(.?.?.?.?)$tag\*(.*?)]/$1$tagvalue$2\[$1$tag$2]/g;
870 my @subf = $field->subfields;
871 for my $i (0..$#subf) {
872 my $subfieldcode = $subf[$i][0];
873 my $subfieldvalue = $subf[$i][1];
874 my $tagsubf = $tag.$subfieldcode;
875 $summary =~ s/\[(.?.?.?.?)$tagsubf(.*?)]/$1$subfieldvalue$2\[$1$tagsubf$2]/g;
879 $summary =~ s/\[(.*?)]//g;
880 $summary =~ s/\n/<br>/g;
881 $oldbiblio->{summary} = $summary;
883 # add spans to search term in results
884 foreach my $term ( keys %$span_terms_hashref ) {
887 my $old_term = $term;
888 if ( length($term) > 3 ) {
889 $term =~ s/(.*=|\)|\(|\+|\.|\?)//g;
891 #FIXME: is there a better way to do this?
892 $oldbiblio->{'title'} =~ s/$term/<span class=term>$&<\/span>/gi;
893 $oldbiblio->{'subtitle'} =~
894 s/$term/<span class=term>$&<\/span>/gi;
896 $oldbiblio->{'author'} =~ s/$term/<span class=term>$&<\/span>/gi;
897 $oldbiblio->{'publishercode'} =~ s/$term/<span class=term>$&<\/span>/gi;
898 $oldbiblio->{'place'} =~ s/$term/<span class=term>$&<\/span>/gi;
899 $oldbiblio->{'pages'} =~ s/$term/<span class=term>$&<\/span>/gi;
900 $oldbiblio->{'notes'} =~ s/$term/<span class=term>$&<\/span>/gi;
901 $oldbiblio->{'size'} =~ s/$term/<span class=term>$&<\/span>/gi;
911 $oldbiblio->{'toggle'} = $toggle;
912 my @fields = $marcrecord->field($itemtag);
915 my $ordered_count = 0;
916 my $onloan_count = 0;
917 my $wthdrawn_count = 0;
918 my $itemlost_count = 0;
919 my $itembinding_count = 0;
922 foreach my $field (@fields) {
924 foreach my $code ( keys %subfieldstosearch ) {
925 $item->{$code} = $field->subfield( $subfieldstosearch{$code} );
927 if ( $item->{wthdrawn} ) {
930 elsif ( $item->{notforloan} == -1 ) {
934 elsif ( $item->{itemlost} ) {
937 elsif ( $item->{binding} ) {
938 $itembinding_count++;
940 elsif ( ( $item->{onloan} ) && ( $item->{onloan} != '0000-00-00' ) )
947 if ( $item->{'homebranch'} ) {
948 $items->{ $item->{'homebranch'} }->{count}++;
952 elsif ( $item->{'holdingbranch'} ) {
953 $items->{ $item->{'homebranch'} }->{count}++;
955 $items->{ $item->{homebranch} }->{itemcallnumber} =
956 $item->{itemcallnumber};
957 $items->{ $item->{homebranch} }->{location} =
960 } # notforloan, item level and biblioitem level
961 for my $key ( keys %$items ) {
965 branchname => $branches{$key},
967 count => $items->{$key}->{count},
968 itemcallnumber => $items->{$key}->{itemcallnumber},
969 location => $items->{$key}->{location},
971 push @items_loop, $this_item;
973 $oldbiblio->{norequests} = $norequests;
974 $oldbiblio->{items_loop} = \@items_loop;
975 $oldbiblio->{onloancount} = $onloan_count;
976 $oldbiblio->{wthdrawncount} = $wthdrawn_count;
977 $oldbiblio->{itemlostcount} = $itemlost_count;
978 $oldbiblio->{bindingcount} = $itembinding_count;
979 $oldbiblio->{orderedcount} = $ordered_count;
982 # Ugh ... this is ugly, I'll re-write it better above then delete it
983 # my $norequests = 1;
987 # foreach my $itm (@items) {
988 # $norequests = 0 unless $itm->{'itemnotforloan'};
991 # $oldbiblio->{'noitems'} = $noitems;
992 # $oldbiblio->{'norequests'} = $norequests;
993 # $oldbiblio->{'even'} = $even = not $even;
994 # $oldbiblio->{'itemcount'} = $counts{'total'};
995 # my $totalitemcounts = 0;
996 # foreach my $key (keys %counts){
997 # if ($key ne 'total'){
998 # $totalitemcounts+= $counts{$key};
999 # $oldbiblio->{'locationhash'}->{$key}=$counts{$key};
1002 # my ($locationtext, $locationtextonly, $notavailabletext) = ('','','');
1003 # foreach (sort keys %{$oldbiblio->{'locationhash'}}) {
1004 # if ($_ eq 'notavailable') {
1005 # $notavailabletext="Not available";
1006 # my $c=$oldbiblio->{'locationhash'}->{$_};
1007 # $oldbiblio->{'not-available-p'}=$c;
1009 # $locationtext.="$_";
1010 # my $c=$oldbiblio->{'locationhash'}->{$_};
1011 # if ($_ eq 'Item Lost') {
1012 # $oldbiblio->{'lost-p'} = $c;
1013 # } elsif ($_ eq 'Withdrawn') {
1014 # $oldbiblio->{'withdrawn-p'} = $c;
1015 # } elsif ($_ eq 'On Loan') {
1016 # $oldbiblio->{'on-loan-p'} = $c;
1018 # $locationtextonly.= $_;
1019 # $locationtextonly.= " ($c)<br/> " if $totalitemcounts > 1;
1021 # if ($totalitemcounts>1) {
1022 # $locationtext.=" ($c)<br/> ";
1026 # if ($notavailabletext) {
1027 # $locationtext.= $notavailabletext;
1029 # $locationtext=~s/, $//;
1031 # $oldbiblio->{'location'} = $locationtext;
1032 # $oldbiblio->{'location-only'} = $locationtextonly;
1033 # $oldbiblio->{'use-location-flags-p'} = 1;
1035 push( @newresults, $oldbiblio );
1040 END { } # module clean-up code here (global destructor)
1047 Koha Developement team <info@koha.org>