← Index
NYTProf Performance Profile   « block view • line view • sub view »
For /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
  Run on Tue Oct 15 11:58:52 2013
Reported on Tue Oct 15 12:01:07 2013

Filename/usr/share/koha/lib/C4/Search.pm
StatementsExecuted 12040 statements in 81.9ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
11127.4ms4.40sC4::Search::::searchResultsC4::Search::searchResults
11120.4ms75.9msC4::Search::::__ANON__[:670]C4::Search::__ANON__[:670]
1772316.54ms6.54msC4::Search::::CORE:regcompC4::Search::CORE:regcomp (opcode)
1114.94ms27.3msC4::Search::::parseQueryC4::Search::parseQuery
21091814.71ms4.71msC4::Search::::CORE:matchC4::Search::CORE:match (opcode)
1113.30ms6.33msC4::Search::::BEGIN@24C4::Search::BEGIN@24
1112.66ms9.92msC4::Search::::BEGIN@36C4::Search::BEGIN@36
1112.06ms11.1msC4::Search::::BEGIN@29C4::Search::BEGIN@29
5581911.49ms1.69msC4::Search::::CORE:substC4::Search::CORE:subst (opcode)
111919µs5.79msC4::Search::::BEGIN@34C4::Search::BEGIN@34
111846µs1.04msC4::Search::::BEGIN@25C4::Search::BEGIN@25
8151584µs584µsC4::Search::::CORE:sortC4::Search::CORE:sort (opcode)
111415µs37.8msC4::Search::::buildQueryC4::Search::buildQuery
111309µs143msC4::Search::::getRecordsC4::Search::getRecords
111274µs135msC4::Search::::_ZOOM_event_loopC4::Search::_ZOOM_event_loop
11162µs62µsC4::Search::::getIndexesC4::Search::getIndexes
11152µs55µsC4::Search::::_detect_truncationC4::Search::_detect_truncation
11134µs41µsC4::Search::::BEGIN@18C4::Search::BEGIN@18
11133µs44µsC4::Search::::_build_weighted_queryC4::Search::_build_weighted_query
11128µs178µsC4::Search::::BEGIN@2010C4::Search::BEGIN@2010
11126µs149µsC4::Search::::BEGIN@33C4::Search::BEGIN@33
11124µs45µsC4::Search::::BEGIN@37C4::Search::BEGIN@37
11119µs631µsC4::Search::::BEGIN@23C4::Search::BEGIN@23
11119µs54µsC4::Search::::BEGIN@27C4::Search::BEGIN@27
11119µs608µsC4::Search::::BEGIN@22C4::Search::BEGIN@22
11118µs68µsC4::Search::::BEGIN@35C4::Search::BEGIN@35
11118µs416µsC4::Search::::BEGIN@31C4::Search::BEGIN@31
11117µs23µsC4::Search::::BEGIN@39C4::Search::BEGIN@39
11116µs153µsC4::Search::::BEGIN@32C4::Search::BEGIN@32
11116µs53µsC4::Search::::BEGIN@28C4::Search::BEGIN@28
11115µs64µsC4::Search::::BEGIN@26C4::Search::BEGIN@26
11115µs19µsC4::Search::::BEGIN@21C4::Search::BEGIN@21
11114µs153µsC4::Search::::BEGIN@30C4::Search::BEGIN@30
11112µs112µsC4::Search::::BEGIN@40C4::Search::BEGIN@40
11111µs11µsC4::Search::::BEGIN@38C4::Search::BEGIN@38
1117µs7µsC4::Search::::BEGIN@43C4::Search::BEGIN@43
1115µs5µsC4::Search::::ENDC4::Search::END
0000s0sC4::Search::::AddSearchHistoryC4::Search::AddSearchHistory
0000s0sC4::Search::::FindDuplicateC4::Search::FindDuplicate
0000s0sC4::Search::::GetDistinctValuesC4::Search::GetDistinctValues
0000s0sC4::Search::::GetSearchHistoryC4::Search::GetSearchHistory
0000s0sC4::Search::::SearchAcquisitionsC4::Search::SearchAcquisitions
0000s0sC4::Search::::SimpleSearchC4::Search::SimpleSearch
0000s0sC4::Search::::__ANON__[:2327]C4::Search::__ANON__[:2327]
0000s0sC4::Search::::__ANON__[:297]C4::Search::__ANON__[:297]
0000s0sC4::Search::::_build_stemmed_operandC4::Search::_build_stemmed_operand
0000s0sC4::Search::::_handle_exploding_indexC4::Search::_handle_exploding_index
0000s0sC4::Search::::_remove_stopwordsC4::Search::_remove_stopwords
0000s0sC4::Search::::enabled_staff_search_viewsC4::Search::enabled_staff_search_views
0000s0sC4::Search::::pazGetRecordsC4::Search::pazGetRecords
0000s0sC4::Search::::z3950_search_argsC4::Search::z3950_search_args
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1package C4::Search;
2
3# This file is part of Koha.
4#
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
8# version.
9#
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.
13#
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
17
18348µs249µs
# spent 41µs (34+7) within C4::Search::BEGIN@18 which was called: # once (34µs+7µs) by main::BEGIN@48 at line 18
use strict;
# spent 41µs making 1 call to C4::Search::BEGIN@18 # spent 7µs making 1 call to strict::import
19#use warnings; FIXME - Bug 2505
2013µsrequire Exporter;
21331µs223µs
# spent 19µs (15+4) within C4::Search::BEGIN@21 which was called: # once (15µs+4µs) by main::BEGIN@48 at line 21
use C4::Context;
# spent 19µs making 1 call to C4::Search::BEGIN@21 # spent 4µs making 1 call to C4::Context::import
22345µs21.20ms
# spent 608µs (19+590) within C4::Search::BEGIN@22 which was called: # once (19µs+590µs) by main::BEGIN@48 at line 22
use C4::Biblio; # GetMarcFromKohaField, GetBiblioData
# spent 608µs making 1 call to C4::Search::BEGIN@22 # spent 590µs making 1 call to Exporter::import
23351µs21.24ms
# spent 631µs (19+611) within C4::Search::BEGIN@23 which was called: # once (19µs+611µs) by main::BEGIN@48 at line 23
use C4::Koha; # getFacets
# spent 631µs making 1 call to C4::Search::BEGIN@23 # spent 611µs making 1 call to Exporter::import
243221µs26.38ms
# spent 6.33ms (3.30+3.03) within C4::Search::BEGIN@24 which was called: # once (3.30ms+3.03ms) by main::BEGIN@48 at line 24
use Lingua::Stem;
# spent 6.33ms making 1 call to C4::Search::BEGIN@24 # spent 53µs making 1 call to Exporter::import
253188µs11.04ms
# spent 1.04ms (846µs+196µs) within C4::Search::BEGIN@25 which was called: # once (846µs+196µs) by main::BEGIN@48 at line 25
use C4::Search::PazPar2;
# spent 1.04ms making 1 call to C4::Search::BEGIN@25
26335µs276µs
# spent 64µs (15+49) within C4::Search::BEGIN@26 which was called: # once (15µs+49µs) by main::BEGIN@48 at line 26
use XML::Simple;
# spent 64µs making 1 call to C4::Search::BEGIN@26 # spent 12µs making 1 call to XML::Simple::import
27338µs289µs
# spent 54µs (19+35) within C4::Search::BEGIN@27 which was called: # once (19µs+35µs) by main::BEGIN@48 at line 27
use C4::Dates qw(format_date);
# spent 54µs making 1 call to C4::Search::BEGIN@27 # spent 35µs making 1 call to Exporter::import
28332µs290µs
# spent 53µs (16+37) within C4::Search::BEGIN@28 which was called: # once (16µs+37µs) by main::BEGIN@48 at line 28
use C4::Members qw(GetHideLostItemsPreference);
# spent 53µs making 1 call to C4::Search::BEGIN@28 # spent 37µs making 1 call to Exporter::import
293141µs211.3ms
# spent 11.1ms (2.06+9.08) within C4::Search::BEGIN@29 which was called: # once (2.06ms+9.08ms) by main::BEGIN@48 at line 29
use C4::XSLT;
# spent 11.1ms making 1 call to C4::Search::BEGIN@29 # spent 121µs making 1 call to Exporter::import
30332µs2293µs
# spent 153µs (14+139) within C4::Search::BEGIN@30 which was called: # once (14µs+139µs) by main::BEGIN@48 at line 30
use C4::Branch;
# spent 153µs making 1 call to C4::Search::BEGIN@30 # spent 140µs making 1 call to Exporter::import
31340µs2813µs
# spent 416µs (18+397) within C4::Search::BEGIN@31 which was called: # once (18µs+397µs) by main::BEGIN@48 at line 31
use C4::Reserves; # GetReserveStatus
# spent 416µs making 1 call to C4::Search::BEGIN@31 # spent 397µs making 1 call to Exporter::import
32354µs2289µs
# spent 153µs (16+137) within C4::Search::BEGIN@32 which was called: # once (16µs+137µs) by main::BEGIN@48 at line 32
use C4::Debug;
# spent 153µs making 1 call to C4::Search::BEGIN@32 # spent 137µs making 1 call to Exporter::import
33360µs2272µs
# spent 149µs (26+123) within C4::Search::BEGIN@33 which was called: # once (26µs+123µs) by main::BEGIN@48 at line 33
use C4::Charset;
# spent 149µs making 1 call to C4::Search::BEGIN@33 # spent 123µs making 1 call to Exporter::import
343170µs25.85ms
# spent 5.79ms (919µs+4.87) within C4::Search::BEGIN@34 which was called: # once (919µs+4.87ms) by main::BEGIN@48 at line 34
use YAML;
# spent 5.79ms making 1 call to C4::Search::BEGIN@34 # spent 54µs making 1 call to Exporter::import
35339µs2119µs
# spent 68µs (18+50) within C4::Search::BEGIN@35 which was called: # once (18µs+50µs) by main::BEGIN@48 at line 35
use URI::Escape;
# spent 68µs making 1 call to C4::Search::BEGIN@35 # spent 50µs making 1 call to Exporter::import
363167µs29.95ms
# spent 9.92ms (2.66+7.26) within C4::Search::BEGIN@36 which was called: # once (2.66ms+7.26ms) by main::BEGIN@48 at line 36
use Business::ISBN;
# spent 9.92ms making 1 call to C4::Search::BEGIN@36 # spent 33µs making 1 call to Exporter::import
37338µs267µs
# spent 45µs (24+21) within C4::Search::BEGIN@37 which was called: # once (24µs+21µs) by main::BEGIN@48 at line 37
use MARC::Record;
# spent 45µs making 1 call to C4::Search::BEGIN@37 # spent 21µs making 1 call to Exporter::import
38330µs111µs
# spent 11µs within C4::Search::BEGIN@38 which was called: # once (11µs+0s) by main::BEGIN@48 at line 38
use MARC::Field;
# spent 11µs making 1 call to C4::Search::BEGIN@38
39340µs228µs
# spent 23µs (17+6) within C4::Search::BEGIN@39 which was called: # once (17µs+6µs) by main::BEGIN@48 at line 39
use utf8;
# spent 23µs making 1 call to C4::Search::BEGIN@39 # spent 6µs making 1 call to utf8::import
40352µs2211µs
# spent 112µs (12+99) within C4::Search::BEGIN@40 which was called: # once (12µs+99µs) by main::BEGIN@48 at line 40
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG);
# spent 112µs making 1 call to C4::Search::BEGIN@40 # spent 99µs making 1 call to vars::import
41
42# set the version for version checking
43
# spent 7µs within C4::Search::BEGIN@43 which was called: # once (7µs+0s) by main::BEGIN@48 at line 46
BEGIN {
4428µs $VERSION = 3.07.00.049;
45 $DEBUG = ($ENV{DEBUG}) ? 1 : 0;
4619.19ms17µs}
# spent 7µs making 1 call to C4::Search::BEGIN@43
47
48=head1 NAME
49
- -
64125µs@ISA = qw(Exporter);
6516µs@EXPORT = qw(
66 &FindDuplicate
67 &SimpleSearch
68 &searchResults
69 &getRecords
70 &buildQuery
71 &AddSearchHistory
72 &GetDistinctValues
73 &enabled_staff_search_views
74 &SimpleSearch
75);
76
77# make all your functions, whether exported or not;
78
79=head2 FindDuplicate
80
- -
87sub FindDuplicate {
88 my ($record) = @_;
89 my $dbh = C4::Context->dbh;
90 my $result = TransformMarcToKoha( $dbh, $record, '' );
91 my $sth;
92 my $query;
93 my $search;
94 my $type;
95 my ( $biblionumber, $title );
96
97 # search duplicate on ISBN, easy and fast..
98 # ... normalize first
99 if ( $result->{isbn} ) {
100 $result->{isbn} =~ s/\(.*$//;
101 $result->{isbn} =~ s/\s+$//;
102 $query = "isbn:$result->{isbn}";
103 }
104 else {
105 my $QParser;
106 $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
107 my $titleindex;
108 my $authorindex;
109 my $op;
110
111 if ($QParser) {
112 $titleindex = 'title|exact';
113 $authorindex = 'author|exact';
114 $op = '&&';
115 $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
116 } else {
117 $titleindex = 'ti,ext';
118 $authorindex = 'au,ext';
119 $op = 'and';
120 }
121
122 $result->{title} =~ s /\\//g;
123 $result->{title} =~ s /\"//g;
124 $result->{title} =~ s /\(//g;
125 $result->{title} =~ s /\)//g;
126
127 # FIXME: instead of removing operators, could just do
128 # quotes around the value
129 $result->{title} =~ s/(and|or|not)//g;
130 $query = "$titleindex:\"$result->{title}\"";
131 if ( $result->{author} ) {
132 $result->{author} =~ s /\\//g;
133 $result->{author} =~ s /\"//g;
134 $result->{author} =~ s /\(//g;
135 $result->{author} =~ s /\)//g;
136
137 # remove valid operators
138 $result->{author} =~ s/(and|or|not)//g;
139 $query .= " $op $authorindex:\"$result->{author}\"";
140 }
141 }
142
143 my ( $error, $searchresults, undef ) = SimpleSearch($query); # FIXME :: hardcoded !
144 my @results;
145 if (!defined $error) {
146 foreach my $possible_duplicate_record (@{$searchresults}) {
147 my $marcrecord =
148 MARC::Record->new_from_usmarc($possible_duplicate_record);
149 my $result = TransformMarcToKoha( $dbh, $marcrecord, '' );
150
151 # FIXME :: why 2 $biblionumber ?
152 if ($result) {
153 push @results, $result->{'biblionumber'};
154 push @results, $result->{'title'};
155 }
156 }
157 }
158 return @results;
159}
160
161=head2 SimpleSearch
162
- -
223sub SimpleSearch {
224 my ( $query, $offset, $max_results, $servers ) = @_;
225
226 return ( 'No query entered', undef, undef ) unless $query;
227 # FIXME hardcoded value. See catalog/search.pl & opac-search.pl too.
228 my @servers = defined ( $servers ) ? @$servers : ( 'biblioserver' );
229 my @zoom_queries;
230 my @tmpresults;
231 my @zconns;
232 my $results = [];
233 my $total_hits = 0;
234
235 my $QParser;
236 $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') && ! ($query =~ m/\w,\w|\w=\w/));
237 if ($QParser) {
238 $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
239 }
240
241 # Initialize & Search Zebra
242 for ( my $i = 0 ; $i < @servers ; $i++ ) {
243 eval {
244 $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
245 if ($QParser) {
246 $query =~ s/=/:/g;
247 $QParser->parse( $query );
248 $query = $QParser->target_syntax($servers[$i]);
249 $zoom_queries[$i] = new ZOOM::Query::PQF( $query, $zconns[$i]);
250 } else {
251 $query =~ s/:/=/g;
252 $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]);
253 }
254 $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] );
255
256 # error handling
257 my $error =
258 $zconns[$i]->errmsg() . " ("
259 . $zconns[$i]->errcode() . ") "
260 . $zconns[$i]->addinfo() . " "
261 . $zconns[$i]->diagset();
262
263 return ( $error, undef, undef ) if $zconns[$i]->errcode();
264 };
265 if ($@) {
266
267 # caught a ZOOM::Exception
268 my $error =
269 $@->message() . " ("
270 . $@->code() . ") "
271 . $@->addinfo() . " "
272 . $@->diagset();
273 warn $error." for query: $query";
274 return ( $error, undef, undef );
275 }
276 }
277
278 _ZOOM_event_loop(
279 \@zconns,
280 \@tmpresults,
281 sub {
282 my ($i, $size) = @_;
283 my $first_record = defined($offset) ? $offset + 1 : 1;
284 my $hits = $tmpresults[ $i - 1 ]->size();
285 $total_hits += $hits;
286 my $last_record = $hits;
287 if ( defined $max_results && $offset + $max_results < $hits ) {
288 $last_record = $offset + $max_results;
289 }
290
291 for my $j ( $first_record .. $last_record ) {
292 my $record =
293 $tmpresults[ $i - 1 ]->record( $j - 1 )->raw()
294 ; # 0 indexed
295 push @{$results}, $record;
296 }
297 }
298 );
299
300 foreach my $zoom_query (@zoom_queries) {
301 $zoom_query->destroy();
302 }
303
304 return ( undef, $results, $total_hits );
305}
306
307=head2 getRecords
308
- -
323
# spent 143ms (309µs+143) within C4::Search::getRecords which was called: # once (309µs+143ms) by main::RUNTIME at line 523 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub getRecords {
324 my (
32528313µs $koha_query, $simple_query, $sort_by_ref, $servers_ref,
326 $results_per_page, $offset, $expanded_facet, $branches,
327 $itemtypes, $query_type, $scan, $opac
328 ) = @_;
329
330 my @servers = @$servers_ref;
331 my @sort_by = @$sort_by_ref;
332
333 # Initialize variables for the ZOOM connection and results object
334 my $zconn;
335 my @zconns;
336 my @results;
337 my $results_hashref = ();
338
339 # Initialize variables for the faceted results objects
340 my $facets_counter = ();
341 my $facets_info = ();
34211.94ms my $facets = getFacets();
# spent 1.94ms making 1 call to C4::Koha::getFacets
34311.68ms my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets')||20;
# spent 1.68ms making 1 call to C4::Context::preference
344
345 my @facets_loop; # stores the ref to array of hashes for template facets loop
346
347 ### LOOP THROUGH THE SERVERS
348 for ( my $i = 0 ; $i < @servers ; $i++ ) {
3491889µs $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
# spent 889µs making 1 call to C4::Context::Zconn
350
351# perform the search, create the results objects
352# if this is a local search, use the $koha-query, if it's a federated one, use the federated-query
353113µs my $query_to_use = ($servers[$i] =~ /biblioserver/) ? $koha_query : $simple_query;
# spent 13µs making 1 call to C4::Search::CORE:match
354
355 #$query_to_use = $simple_query if $scan;
356 warn $simple_query if ( $scan and $DEBUG );
357
358 # Check if we've got a query_type defined, if so, use it
359 eval {
360 if ($query_type) {
361 if ($query_type =~ /^ccl/) {
362 $query_to_use =~ s/\:/\=/g; # change : to = last minute (FIXME)
363 $results[$i] = $zconns[$i]->search(new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i]));
364 } elsif ($query_type =~ /^cql/) {
365 $results[$i] = $zconns[$i]->search(new ZOOM::Query::CQL($query_to_use, $zconns[$i]));
366 } elsif ($query_type =~ /^pqf/) {
367 $results[$i] = $zconns[$i]->search(new ZOOM::Query::PQF($query_to_use, $zconns[$i]));
368 } else {
369 warn "Unknown query_type '$query_type'. Results undetermined.";
370 }
371 } elsif ($scan) {
372 $results[$i] = $zconns[$i]->scan( new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i]));
373 } else {
37423.40ms $results[$i] = $zconns[$i]->search(new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i]));
# spent 3.18ms making 1 call to ZOOM::Query::CCL2RPN::new # spent 213µs making 1 call to ZOOM::Connection::search
375 }
376 };
377 if ($@) {
378 warn "WARNING: query problem with $query_to_use " . $@;
379 }
380
381 # Concatenate the sort_by limits and pass them to the results object
382 # Note: sort will override rank
383 my $sort_by;
384 foreach my $sort (@sort_by) {
385 if ( $sort eq "author_az" || $sort eq "author_asc" ) {
386 $sort_by .= "1=1003 <i ";
387 }
388 elsif ( $sort eq "author_za" || $sort eq "author_dsc" ) {
389 $sort_by .= "1=1003 >i ";
390 }
391 elsif ( $sort eq "popularity_asc" ) {
392 $sort_by .= "1=9003 <i ";
393 }
394 elsif ( $sort eq "popularity_dsc" ) {
395 $sort_by .= "1=9003 >i ";
396 }
397 elsif ( $sort eq "call_number_asc" ) {
398 $sort_by .= "1=8007 <i ";
399 }
400 elsif ( $sort eq "call_number_dsc" ) {
401 $sort_by .= "1=8007 >i ";
402 }
403 elsif ( $sort eq "pubdate_asc" ) {
404 $sort_by .= "1=31 <i ";
405 }
406 elsif ( $sort eq "pubdate_dsc" ) {
407 $sort_by .= "1=31 >i ";
408 }
409 elsif ( $sort eq "acqdate_asc" ) {
410 $sort_by .= "1=32 <i ";
411 }
412 elsif ( $sort eq "acqdate_dsc" ) {
413 $sort_by .= "1=32 >i ";
414 }
415 elsif ( $sort eq "title_az" || $sort eq "title_asc" ) {
416 $sort_by .= "1=4 <i ";
417 }
418 elsif ( $sort eq "title_za" || $sort eq "title_dsc" ) {
419 $sort_by .= "1=4 >i ";
420 }
421 else {
422 warn "Ignoring unrecognized sort '$sort' requested" if $sort_by;
423 }
424 }
425 if ($sort_by && !$scan) {
426 if ( $results[$i]->sort( "yaz", $sort_by ) < 0 ) {
427 warn "WARNING sort $sort_by failed";
428 }
429 }
430 } # finished looping through servers
431
432 # The big moment: asynchronously retrieve results from all servers
433 _ZOOM_event_loop(
434 \@zconns,
435 \@results,
436
# spent 75.9ms (20.4+55.5) within C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] which was called: # once (20.4ms+55.5ms) by C4::Search::_ZOOM_event_loop at line 2352
sub {
437733233.1ms my ( $i, $size ) = @_;
438 my $results_hash;
439
440 # loop through the results
441 $results_hash->{'hits'} = $size;
442 my $times;
443 if ( $offset + $results_per_page <= $size ) {
444 $times = $offset + $results_per_page;
445 }
446 else {
447 $times = $size;
448 }
449 for ( my $j = $offset ; $j < $times ; $j++ ) {
450 my $records_hash;
451 my $record;
452
453 ## Check if it's an index scan
454 if ($scan) {
455 my ( $term, $occ ) = $results[ $i - 1 ]->term($j);
456
457 # here we create a minimal MARC record and hand it off to the
458 # template just like a normal result ... perhaps not ideal, but
459 # it works for now
460 my $tmprecord = MARC::Record->new();
461 $tmprecord->encoding('UTF-8');
462 my $tmptitle;
463 my $tmpauthor;
464
465 # the minimal record in author/title (depending on MARC flavour)
466 if ( C4::Context->preference("marcflavour") eq
467 "UNIMARC" )
468 {
469 $tmptitle = MARC::Field->new(
470 '200', ' ', ' ',
471 a => $term,
472 f => $occ
473 );
474 $tmprecord->append_fields($tmptitle);
475 }
476 else {
477 $tmptitle =
478 MARC::Field->new( '245', ' ', ' ', a => $term, );
479 $tmpauthor =
480 MARC::Field->new( '100', ' ', ' ', a => $occ, );
481 $tmprecord->append_fields($tmptitle);
482 $tmprecord->append_fields($tmpauthor);
483 }
484 $results_hash->{'RECORDS'}[$j] =
485 $tmprecord->as_usmarc();
486 }
487
488 # not an index scan
489 else {
4905016.5ms $record = $results[ $i - 1 ]->record($j)->raw();
# spent 14.0ms making 25 calls to ZOOM::ResultSet::record, avg 561µs/call # spent 2.49ms making 25 calls to ZOOM::Record::raw, avg 100µs/call
491
492 # warn "RECORD $j:".$record;
493 $results_hash->{'RECORDS'}[$j] = $record;
494 }
495
496 }
497 $results_hashref->{ $servers[ $i - 1 ] } = $results_hash;
498
499# Fill the facets while we're looping, but only for the biblioserver and not for a scan
50017µs if ( !$scan && $servers[ $i - 1 ] =~ /biblioserver/ ) {
# spent 7µs making 1 call to C4::Search::CORE:match
501
502 my $jmax =
503 $size > $facets_maxrecs ? $facets_maxrecs : $size;
504 for my $facet (@$facets) {
505 for ( my $j = 0 ; $j < $jmax ; $j++ ) {
50635023.4ms my $render_record =
# spent 13.5ms making 175 calls to ZOOM::ResultSet::record, avg 77µs/call # spent 9.96ms making 175 calls to ZOOM::Record::render, avg 57µs/call
507 $results[ $i - 1 ]->record($j)->render();
508 my @used_datas = ();
509 foreach my $tag ( @{ $facet->{tags} } ) {
510
511 # avoid first line
512 my $tag_num = substr( $tag, 0, 3 );
513 my $letters = substr( $tag, 3 );
514 my $field_pattern =
515 '\n' . $tag_num . ' ([^z][^\n]+)';
516 $field_pattern = '\n' . $tag_num . ' ([^\n]+)'
517 if ( int($tag_num) < 10 );
5185503.62ms my @field_tokens =
# spent 1.94ms making 275 calls to C4::Search::CORE:regcomp, avg 7µs/call # spent 1.68ms making 275 calls to C4::Search::CORE:match, avg 6µs/call
519 ( $render_record =~ /$field_pattern/g );
520 foreach my $field_token (@field_tokens) {
5212521.75ms my @subf = ( $field_token =~
# spent 1.75ms making 252 calls to C4::Search::CORE:match, avg 7µs/call
522 /\$([a-zA-Z0-9]) ([^\$]+)/g );
523 my @values;
524 for ( my $i = 0 ; $i < @subf ; $i += 2 ) {
52529925.74ms if ( $letters =~ $subf[$i] ) {
# spent 4.58ms making 1496 calls to C4::Search::CORE:regcomp, avg 3µs/call # spent 1.16ms making 1496 calls to C4::Search::CORE:match, avg 778ns/call
526 my $value = $subf[ $i + 1 ];
527252398µs $value =~ s/^ *//;
# spent 398µs making 252 calls to C4::Search::CORE:subst, avg 2µs/call
528252886µs $value =~ s/ *$//;
# spent 886µs making 252 calls to C4::Search::CORE:subst, avg 4µs/call
529 push @values, $value;
530 }
531 }
532 my $data = join( $facet->{sep}, @values );
533 unless ( $data ~~ @used_datas ) {
534 $facets_counter->{ $facet->{idx} }
535 ->{$data}++;
536 push @used_datas, $data;
537 }
538 } # fields
539 } # field codes
540 } # records
541 $facets_info->{ $facet->{idx} }->{label_value} =
542 $facet->{label};
543 $facets_info->{ $facet->{idx} }->{expanded} =
544 $facet->{expanded};
545 } # facets
546 }
547
548 # warn "connection ", $i-1, ": $size hits";
549 # warn $results[$i-1]->record(0)->render() if $size > 0;
550
551 # BUILD FACETS
55213µs if ( $servers[ $i - 1 ] =~ /biblioserver/ ) {
# spent 3µs making 1 call to C4::Search::CORE:match
553115µs for my $link_value (
# spent 15µs making 1 call to C4::Search::CORE:sort
554 sort { $facets_counter->{$b} <=> $facets_counter->{$a} }
555 keys %$facets_counter
556 )
557 {
558 my $expandable;
559 my $number_of_facets;
560 my @this_facets_array;
5615285µs for my $one_facet (
# spent 285µs making 5 calls to C4::Search::CORE:sort, avg 57µs/call
562 sort {
563 $facets_counter->{$link_value}
564 ->{$b} <=> $facets_counter->{$link_value}
565 ->{$a}
566 } keys %{ $facets_counter->{$link_value} }
567 )
568 {
569 $number_of_facets++;
570 if ( ( $number_of_facets < 6 )
571 || ( $expanded_facet eq $link_value )
572 || ( $facets_info->{$link_value}->{'expanded'} )
573 )
574 {
575
576# Sanitize the link value : parenthesis, question and exclamation mark will cause errors with CCL
577 my $facet_link_value = $one_facet;
578112µs24570µs $facet_link_value =~ s/[()!?¡¿؟]/ /g;
# spent 368µs making 23 calls to C4::Search::CORE:subst, avg 16µs/call # spent 202µs making 1 call to utf8::SWASHNEW
579
580 # fix the length that will display in the label,
581 my $facet_label_value = $one_facet;
582232.39ms my $facet_max_length = C4::Context->preference(
# spent 2.39ms making 23 calls to C4::Context::preference, avg 104µs/call
583 'FacetLabelTruncationLength')
584 || 20;
585 $facet_label_value =
586 substr( $one_facet, 0, $facet_max_length )
587 . "..."
588 if length($facet_label_value) >
589 $facet_max_length;
590
591 # if it's a branch, label by the name, not the code,
5922333µs if ( $link_value =~ /branch/ ) {
# spent 33µs making 23 calls to C4::Search::CORE:match, avg 1µs/call
593 if ( defined $branches
594 && ref($branches) eq "HASH"
595 && defined $branches->{$one_facet}
596 && ref( $branches->{$one_facet} ) eq
597 "HASH" )
598 {
599 $facet_label_value =
600 $branches->{$one_facet}
601 ->{'branchname'};
602 }
603 else {
604 $facet_label_value = "*";
605 }
606 }
607
608 # if it's a itemtype, label by the name, not the code,
6092325µs if ( $link_value =~ /itype/ ) {
# spent 25µs making 23 calls to C4::Search::CORE:match, avg 1µs/call
610 if ( defined $itemtypes
611 && ref($itemtypes) eq "HASH"
612 && defined $itemtypes->{$one_facet}
613 && ref( $itemtypes->{$one_facet} ) eq
614 "HASH" )
615 {
616 $facet_label_value =
617 $itemtypes->{$one_facet}
618 ->{'description'};
619 }
620 }
621
622 # also, if it's a location code, use the name instead of the code
623238µs if ( $link_value =~ /location/ ) {
# spent 8µs making 23 calls to C4::Search::CORE:match, avg 361ns/call
624 $facet_label_value =
625 GetKohaAuthorisedValueLib( 'LOC',
626 $one_facet, $opac );
627 }
628
629 # but we're down with the whole label being in the link's title.
630 push @this_facets_array,
631 {
632 facet_count =>
633 $facets_counter->{$link_value}
634 ->{$one_facet},
635 facet_label_value => $facet_label_value,
636 facet_title_value => $one_facet,
637 facet_link_value => $facet_link_value,
638 type_link_value => $link_value,
639 }
640 if ($facet_label_value);
641 }
642 }
643
644 # handle expanded option
645 unless ( $facets_info->{$link_value}->{'expanded'} ) {
646 $expandable = 1
647 if ( ( $number_of_facets > 6 )
648 && ( $expanded_facet ne $link_value ) );
649 }
650617µs push @facets_loop,
# spent 9µs making 5 calls to C4::Search::CORE:match, avg 2µs/call # spent 8µs making 1 call to C4::Context::preference
651 {
652 type_link_value => $link_value,
653 type_id => $link_value . "_id",
654 "type_label_"
655 . $facets_info->{$link_value}->{'label_value'} =>
656 1,
657 facets => \@this_facets_array,
658 expandable => $expandable,
659 expand => $link_value,
660 }
661 unless (
662 (
663 $facets_info->{$link_value}->{'label_value'} =~
664 /Libraries/
665 )
666 and ( C4::Context->preference('singleBranchMode') )
667 );
668 }
669 }
670 }
6711135ms );
# spent 135ms making 1 call to C4::Search::_ZOOM_event_loop
672 return ( undef, $results_hashref, \@facets_loop );
673}
674
675sub pazGetRecords {
676 my (
677 $koha_query, $simple_query, $sort_by_ref, $servers_ref,
678 $results_per_page, $offset, $expanded_facet, $branches,
679 $query_type, $scan
680 ) = @_;
681
682 my $paz = C4::Search::PazPar2->new(C4::Context->config('pazpar2url'));
683 $paz->init();
684 $paz->search($simple_query);
685 sleep 1; # FIXME: WHY?
686
687 # do results
688 my $results_hashref = {};
689 my $stats = XMLin($paz->stat);
690 my $results = XMLin($paz->show($offset, $results_per_page, 'work-title:1'), forcearray => 1);
691
692 # for a grouped search result, the number of hits
693 # is the number of groups returned; 'bib_hits' will have
694 # the total number of bibs.
695 $results_hashref->{'biblioserver'}->{'hits'} = $results->{'merged'}->[0];
696 $results_hashref->{'biblioserver'}->{'bib_hits'} = $stats->{'hits'};
697
698 HIT: foreach my $hit (@{ $results->{'hit'} }) {
699 my $recid = $hit->{recid}->[0];
700
701 my $work_title = $hit->{'md-work-title'}->[0];
702 my $work_author;
703 if (exists $hit->{'md-work-author'}) {
704 $work_author = $hit->{'md-work-author'}->[0];
705 }
706 my $group_label = (defined $work_author) ? "$work_title / $work_author" : $work_title;
707
708 my $result_group = {};
709 $result_group->{'group_label'} = $group_label;
710 $result_group->{'group_merge_key'} = $recid;
711
712 my $count = 1;
713 if (exists $hit->{count}) {
714 $count = $hit->{count}->[0];
715 }
716 $result_group->{'group_count'} = $count;
717
718 for (my $i = 0; $i < $count; $i++) {
719 # FIXME -- may need to worry about diacritics here
720 my $rec = $paz->record($recid, $i);
721 push @{ $result_group->{'RECORDS'} }, $rec;
722 }
723
724 push @{ $results_hashref->{'biblioserver'}->{'GROUPS'} }, $result_group;
725 }
726
727 # pass through facets
728 my $termlist_xml = $paz->termlist('author,subject');
729 my $terms = XMLin($termlist_xml, forcearray => 1);
730 my @facets_loop = ();
731 #die Dumper($results);
732# foreach my $list (sort keys %{ $terms->{'list'} }) {
733# my @facets = ();
734# foreach my $facet (sort @{ $terms->{'list'}->{$list}->{'term'} } ) {
735# push @facets, {
736# facet_label_value => $facet->{'name'}->[0],
737# };
738# }
739# push @facets_loop, ( {
740# type_label => $list,
741# facets => \@facets,
742# } );
743# }
744
745 return ( undef, $results_hashref, \@facets_loop );
746}
747
748# STOPWORDS
749sub _remove_stopwords {
750 my ( $operand, $index ) = @_;
751 my @stopwords_removed;
752
753 # phrase and exact-qualified indexes shouldn't have stopwords removed
754 if ( $index !~ m/phr|ext/ ) {
755
756# remove stopwords from operand : parse all stopwords & remove them (case insensitive)
757# we use IsAlpha unicode definition, to deal correctly with diacritics.
758# otherwise, a French word like "leçon" woudl be split into "le" "çon", "le"
759# is a stopword, we'd get "çon" and wouldn't find anything...
760#
761 foreach ( keys %{ C4::Context->stopwords } ) {
762 next if ( $_ =~ /(and|or|not)/ ); # don't remove operators
763 if ( my ($matched) = ($operand =~
764 /([^\X\p{isAlnum}]\Q$_\E[^\X\p{isAlnum}]|[^\X\p{isAlnum}]\Q$_\E$|^\Q$_\E[^\X\p{isAlnum}])/gi))
765 {
766 $operand =~ s/\Q$matched\E/ /gi;
767 push @stopwords_removed, $_;
768 }
769 }
770 }
771 return ( $operand, \@stopwords_removed );
772}
773
774# TRUNCATION
775
# spent 55µs (52+3) within C4::Search::_detect_truncation which was called: # once (52µs+3µs) by C4::Search::buildQuery at line 1418
sub _detect_truncation {
776761µs my ( $operand, $index ) = @_;
777 my ( @nontruncated, @righttruncated, @lefttruncated, @rightlefttruncated,
778 @regexpr );
7791600ns $operand =~ s/^ //g;
# spent 600ns making 1 call to C4::Search::CORE:subst
780 my @wordlist = split( /\s/, $operand );
781 foreach my $word (@wordlist) {
78233µs if ( $word =~ s/^\*([^\*]+)\*$/$1/ ) {
# spent 3µs making 3 calls to C4::Search::CORE:subst, avg 867ns/call
783 push @rightlefttruncated, $word;
784 }
785 elsif ( $word =~ s/^\*([^\*]+)$/$1/ ) {
786 push @lefttruncated, $word;
787 }
788 elsif ( $word =~ s/^([^\*]+)\*$/$1/ ) {
789 push @righttruncated, $word;
790 }
791 elsif ( index( $word, "*" ) < 0 ) {
792 push @nontruncated, $word;
793 }
794 else {
795 push @regexpr, $word;
796 }
797 }
798 return (
799 \@nontruncated, \@righttruncated, \@lefttruncated,
800 \@rightlefttruncated, \@regexpr
801 );
802}
803
804# STEMMING
805sub _build_stemmed_operand {
806 my ($operand,$lang) = @_;
807 require Lingua::Stem::Snowball ;
808 my $stemmed_operand=q{};
809
810 # If operand contains a digit, it is almost certainly an identifier, and should
811 # not be stemmed. This is particularly relevant for ISBNs and ISSNs, which
812 # can contain the letter "X" - for example, _build_stemmend_operand would reduce
813 # "014100018X" to "x ", which for a MARC21 database would bring up irrelevant
814 # results (e.g., "23 x 29 cm." from the 300$c). Bug 2098.
815 return $operand if $operand =~ /\d/;
816
817# FIXME: the locale should be set based on the user's language and/or search choice
818 #warn "$lang";
819 # Make sure we only use the first two letters from the language code
820 $lang = lc(substr($lang, 0, 2));
821 # The language codes for the two variants of Norwegian will now be "nb" and "nn",
822 # none of which Lingua::Stem::Snowball can use, so we need to "translate" them
823 if ($lang eq 'nb' || $lang eq 'nn') {
824 $lang = 'no';
825 }
826 my $stemmer = Lingua::Stem::Snowball->new( lang => $lang,
827 encoding => "UTF-8" );
828
829 my @words = split( / /, $operand );
830 my @stems = $stemmer->stem(\@words);
831 for my $stem (@stems) {
832 $stemmed_operand .= "$stem";
833 $stemmed_operand .= "?"
834 unless ( $stem =~ /(and$|or$|not$)/ ) || ( length($stem) < 3 );
835 $stemmed_operand .= " ";
836 }
837 warn "STEMMED OPERAND: $stemmed_operand" if $DEBUG;
838 return $stemmed_operand;
839}
840
841# FIELD WEIGHTING
842
# spent 44µs (33+11) within C4::Search::_build_weighted_query which was called: # once (33µs+11µs) by C4::Search::buildQuery at line 1468
sub _build_weighted_query {
843
844# FIELD WEIGHTING - This is largely experimental stuff. What I'm committing works
845# pretty well but could work much better if we had a smarter query parser
8461426µs my ( $operand, $stemmed_operand, $index ) = @_;
84716µs my $stemming = C4::Context->preference("QueryStemming") || 0;
# spent 6µs making 1 call to C4::Context::preference
84813µs my $weight_fields = C4::Context->preference("QueryWeightFields") || 0;
# spent 3µs making 1 call to C4::Context::preference
84912µs my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
# spent 2µs making 1 call to C4::Context::preference
850
851 my $weighted_query .= "(rk=("; # Specifies that we're applying rank
852
853 # Keyword, or, no index specified
854 if ( ( $index eq 'kw' ) || ( !$index ) ) {
855 $weighted_query .=
856 "Title-cover,ext,r1=\"$operand\""; # exact title-cover
857 $weighted_query .= " or ti,ext,r2=\"$operand\""; # exact title
858 $weighted_query .= " or Title-cover,phr,r3=\"$operand\""; # phrase title
859 #$weighted_query .= " or any,ext,r4=$operand"; # exact any
860 #$weighted_query .=" or kw,wrdl,r5=\"$operand\""; # word list any
861 $weighted_query .= " or wrdl,fuzzy,r8=\"$operand\""
862 if $fuzzy_enabled; # add fuzzy, word list
863 $weighted_query .= " or wrdl,right-Truncation,r9=\"$stemmed_operand\""
864 if ( $stemming and $stemmed_operand )
865 ; # add stemming, right truncation
866 $weighted_query .= " or wrdl,r9=\"$operand\"";
867
868 # embedded sorting: 0 a-z; 1 z-a
869 # $weighted_query .= ") or (sort1,aut=1";
870 }
871
872 # Barcode searches should skip this process
873 elsif ( $index eq 'bc' ) {
874 $weighted_query .= "bc=\"$operand\"";
875 }
876
877 # Authority-number searches should skip this process
878 elsif ( $index eq 'an' ) {
879 $weighted_query .= "an=\"$operand\"";
880 }
881
882 # If the index already has more than one qualifier, wrap the operand
883 # in quotes and pass it back (assumption is that the user knows what they
884 # are doing and won't appreciate us mucking up their query
885 elsif ( $index =~ ',' ) {
886 $weighted_query .= " $index=\"$operand\"";
887 }
888
889 #TODO: build better cases based on specific search indexes
890 else {
891 $weighted_query .= " $index,ext,r1=\"$operand\""; # exact index
892 #$weighted_query .= " or (title-sort-az=0 or $index,startswithnt,st-word,r3=$operand #)";
893 $weighted_query .= " or $index,phr,r3=\"$operand\""; # phrase index
894 $weighted_query .= " or $index,wrdl,r6=\"$operand\""; # word list index
895 $weighted_query .= " or $index,wrdl,fuzzy,r8=\"$operand\""
896 if $fuzzy_enabled; # add fuzzy, word list
897 $weighted_query .= " or $index,wrdl,rt,r9=\"$stemmed_operand\""
898 if ( $stemming and $stemmed_operand ); # add stemming, right truncation
899 }
900
901 $weighted_query .= "))"; # close rank specification
902 return $weighted_query;
903}
904
905=head2 getIndexes
906
- -
911
# spent 62µs within C4::Search::getIndexes which was called: # once (62µs+0s) by C4::Search::buildQuery at line 1282
sub getIndexes{
912263µs my @indexes = (
913 # biblio indexes
914 'ab',
915 'Abstract',
916 'acqdate',
917 'allrecords',
918 'an',
919 'Any',
920 'at',
921 'au',
922 'aub',
923 'aud',
924 'audience',
925 'auo',
926 'aut',
927 'Author',
928 'Author-in-order ',
929 'Author-personal-bibliography',
930 'Authority-Number',
931 'authtype',
932 'bc',
933 'Bib-level',
934 'biblionumber',
935 'bio',
936 'biography',
937 'callnum',
938 'cfn',
939 'Chronological-subdivision',
940 'cn-bib-source',
941 'cn-bib-sort',
942 'cn-class',
943 'cn-item',
944 'cn-prefix',
945 'cn-suffix',
946 'cpn',
947 'Code-institution',
948 'Conference-name',
949 'Conference-name-heading',
950 'Conference-name-see',
951 'Conference-name-seealso',
952 'Content-type',
953 'Control-number',
954 'copydate',
955 'Corporate-name',
956 'Corporate-name-heading',
957 'Corporate-name-see',
958 'Corporate-name-seealso',
959 'ctype',
960 'date-entered-on-file',
961 'Date-of-acquisition',
962 'Date-of-publication',
963 'Dewey-classification',
964 'EAN',
965 'extent',
966 'fic',
967 'fiction',
968 'Form-subdivision',
969 'format',
970 'Geographic-subdivision',
971 'he',
972 'Heading',
973 'Heading-use-main-or-added-entry',
974 'Heading-use-series-added-entry ',
975 'Heading-use-subject-added-entry',
976 'Host-item',
977 'id-other',
978 'Illustration-code',
979 'ISBN',
980 'isbn',
981 'ISSN',
982 'issn',
983 'itemtype',
984 'kw',
985 'Koha-Auth-Number',
986 'l-format',
987 'language',
988 'lc-card',
989 'LC-card-number',
990 'lcn',
991 'llength',
992 'ln',
993 'Local-classification',
994 'Local-number',
995 'Match-heading',
996 'Match-heading-see-from',
997 'Material-type',
998 'mc-itemtype',
999 'mc-rtype',
1000 'mus',
1001 'name',
1002 'Music-number',
1003 'Name-geographic',
1004 'Name-geographic-heading',
1005 'Name-geographic-see',
1006 'Name-geographic-seealso',
1007 'nb',
1008 'Note',
1009 'notes',
1010 'ns',
1011 'nt',
1012 'pb',
1013 'Personal-name',
1014 'Personal-name-heading',
1015 'Personal-name-see',
1016 'Personal-name-seealso',
1017 'pl',
1018 'Place-publication',
1019 'pn',
1020 'popularity',
1021 'pubdate',
1022 'Publisher',
1023 'Record-control-number',
1024 'rcn',
1025 'Record-type',
1026 'rtype',
1027 'se',
1028 'See',
1029 'See-also',
1030 'sn',
1031 'Stock-number',
1032 'su',
1033 'Subject',
1034 'Subject-heading-thesaurus',
1035 'Subject-name-personal',
1036 'Subject-subdivision',
1037 'Summary',
1038 'Suppress',
1039 'su-geo',
1040 'su-na',
1041 'su-to',
1042 'su-ut',
1043 'ut',
1044 'UPC',
1045 'Term-genre-form',
1046 'Term-genre-form-heading',
1047 'Term-genre-form-see',
1048 'Term-genre-form-seealso',
1049 'ti',
1050 'Title',
1051 'Title-cover',
1052 'Title-series',
1053 'Title-host',
1054 'Title-uniform',
1055 'Title-uniform-heading',
1056 'Title-uniform-see',
1057 'Title-uniform-seealso',
1058 'totalissues',
1059 'yr',
1060
1061 # items indexes
1062 'acqsource',
1063 'barcode',
1064 'bc',
1065 'branch',
1066 'ccode',
1067 'classification-source',
1068 'cn-sort',
1069 'coded-location-qualifier',
1070 'copynumber',
1071 'damaged',
1072 'datelastborrowed',
1073 'datelastseen',
1074 'holdingbranch',
1075 'homebranch',
1076 'issues',
1077 'item',
1078 'itemnumber',
1079 'itype',
1080 'Local-classification',
1081 'location',
1082 'lost',
1083 'materials-specified',
1084 'mc-ccode',
1085 'mc-itype',
1086 'mc-loc',
1087 'notforloan',
1088 'onloan',
1089 'price',
1090 'renewals',
1091 'replacementprice',
1092 'replacementpricedate',
1093 'reserves',
1094 'restricted',
1095 'stack',
1096 'stocknumber',
1097 'inv',
1098 'uri',
1099 'withdrawn',
1100
1101 # subject related
1102 );
1103
1104 return \@indexes;
1105}
1106
1107=head2 _handle_exploding_index
1108
- -
1117sub _handle_exploding_index {
1118 my ($QParser, $filter, $params, $negate, $server) = @_;
1119 my $index = $filter;
1120 my $term = join(' ', @$params);
1121
1122 return unless ($index =~ m/(su-br|su-na|su-rl)/ && $term);
1123
1124 my $marcflavour = C4::Context->preference('marcflavour');
1125
1126 my $codesubfield = $marcflavour eq 'UNIMARC' ? '5' : 'w';
1127 my $wantedcodes = '';
1128 my @subqueries = ( "\@attr 1=Subject \@attr 4=1 \"$term\"");
1129 my ($error, $results, $total_hits) = SimpleSearch( "he:$term", undef, undef, [ "authorityserver" ] );
1130 foreach my $auth (@$results) {
1131 my $record = MARC::Record->new_from_usmarc($auth);
1132 my @references = $record->field('5..');
1133 if (@references) {
1134 if ($index eq 'su-br') {
1135 $wantedcodes = 'g';
1136 } elsif ($index eq 'su-na') {
1137 $wantedcodes = 'h';
1138 } elsif ($index eq 'su-rl') {
1139 $wantedcodes = '';
1140 }
1141 foreach my $reference (@references) {
1142 my $codes = $reference->subfield($codesubfield);
1143 push @subqueries, '@attr 1=Subject @attr 4=1 "' . $reference->as_string('abcdefghijlmnopqrstuvxyz') . '"' if (($codes && $codes eq $wantedcodes) || !$wantedcodes);
1144 }
1145 }
1146 }
1147 my $query = ' @or ' x (scalar(@subqueries) - 1) . join(' ', @subqueries);
1148 return $query;
1149}
1150
1151=head2 parseQuery
1152
- -
1163
# spent 27.3ms (4.94+22.4) within C4::Search::parseQuery which was called: # once (4.94ms+22.4ms) by C4::Search::buildQuery at line 1253
sub parseQuery {
116420278µs my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
1165
1166 my @operators = $operators ? @$operators : ();
1167 my @indexes = $indexes ? @$indexes : ();
1168 my @operands = $operands ? @$operands : ();
1169 my @limits = $limits ? @$limits : ();
1170 my @sort_by = $sort_by ? @$sort_by : ();
1171
1172 my $query = $operands[0];
1173 my $index;
1174 my $term;
1175 my $query_desc;
1176
1177 my $QParser;
117823.36ms $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') || $query =~ s/^qp=//);
# spent 3.35ms making 1 call to C4::Context::preference # spent 3µs making 1 call to C4::Search::CORE:subst
117921µs undef $QParser if ($query =~ m/^(ccl=|pqf=|cql=)/ || grep (/\w,\w|\w=\w/, @operands, @indexes) );
# spent 1µs making 2 calls to C4::Search::CORE:match, avg 650ns/call
1180 undef $QParser if (scalar @limits > 0);
1181
1182 if ($QParser)
1183 {
1184 $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
1185 $query = '';
1186 for ( my $ii = 0 ; $ii <= @operands ; $ii++ ) {
1187 next unless $operands[$ii];
1188 $query .= $operators[ $ii - 1 ] eq 'or' ? ' || ' : ' && '
1189 if ($query);
1190 if ( $indexes[$ii] =~ m/su-/ ) {
1191 $query .= $indexes[$ii] . '(' . $operands[$ii] . ')';
1192 }
1193 else {
1194 $query .=
1195 ( $indexes[$ii] ? "$indexes[$ii]:" : '' ) . $operands[$ii];
1196 }
1197 }
1198 foreach my $limit (@limits) {
1199 }
1200 if ( scalar(@sort_by) > 0 ) {
1201 my $modifier_re =
1202 '#(' . join( '|', @{ $QParser->modifiers } ) . ')';
1203 $query =~ s/$modifier_re//g;
1204 foreach my $modifier (@sort_by) {
1205 $query .= " #$modifier";
1206 }
1207 }
1208
1209 $query_desc = $query;
1210 $query_desc =~ s/\s+/ /g;
1211 if ( C4::Context->preference("QueryWeightFields") ) {
1212 }
1213 $QParser->add_bib1_filter_map( 'su-br' => 'biblioserver' =>
1214 { 'target_syntax_callback' => \&_handle_exploding_index } );
1215 $QParser->add_bib1_filter_map( 'su-na' => 'biblioserver' =>
1216 { 'target_syntax_callback' => \&_handle_exploding_index } );
1217 $QParser->add_bib1_filter_map( 'su-rl' => 'biblioserver' =>
1218 { 'target_syntax_callback' => \&_handle_exploding_index } );
1219 $QParser->parse($query);
1220 $operands[0] = "pqf=" . $QParser->target_syntax('biblioserver');
1221 }
1222 else {
1223 require Koha::QueryParser::Driver::PQF;
1224112µs my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')';
# spent 12µs making 1 call to OpenILS::QueryParser::modifiers
1225219µs s/$modifier_re//g for @operands;
# spent 17µs making 1 call to C4::Search::CORE:regcomp # spent 2µs making 1 call to C4::Search::CORE:subst
1226 }
1227
1228 return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc);
1229}
1230
1231=head2 buildQuery
1232
- -
1247
# spent 37.8ms (415µs+37.4) within C4::Search::buildQuery which was called: # once (415µs+37.4ms) by main::RUNTIME at line 453 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub buildQuery {
1248106417µs my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
1249
1250 warn "---------\nEnter buildQuery\n---------" if $DEBUG;
1251
1252 my $query_desc;
1253127.3ms ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang);
# spent 27.3ms making 1 call to C4::Search::parseQuery
1254
1255 # dereference
1256 my @operators = $operators ? @$operators : ();
1257 my @indexes = $indexes ? @$indexes : ();
1258 my @operands = $operands ? @$operands : ();
1259 my @limits = $limits ? @$limits : ();
1260 my @sort_by = $sort_by ? @$sort_by : ();
1261
126212.22ms my $stemming = C4::Context->preference("QueryStemming") || 0;
# spent 2.22ms making 1 call to C4::Context::preference
126312.01ms my $auto_truncation = C4::Context->preference("QueryAutoTruncate") || 0;
# spent 2.01ms making 1 call to C4::Context::preference
126412.04ms my $weight_fields = C4::Context->preference("QueryWeightFields") || 0;
# spent 2.04ms making 1 call to C4::Context::preference
126511.87ms my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
# spent 1.87ms making 1 call to C4::Context::preference
126611.68ms my $remove_stopwords = C4::Context->preference("QueryRemoveStopwords") || 0;
# spent 1.68ms making 1 call to C4::Context::preference
1267
1268 my $query = $operands[0];
1269 my $simple_query = $operands[0];
1270
1271 # initialize the variables we're passing back
1272 my $query_cgi;
1273 my $query_type;
1274
1275 my $limit;
1276 my $limit_cgi;
1277 my $limit_desc;
1278
1279 my $stopwords_removed; # flag to determine if stopwords have been removed
1280
1281 my $cclq = 0;
1282162µs my $cclindexes = getIndexes();
# spent 62µs making 1 call to C4::Search::getIndexes
128312µs if ( $query !~ /\s*ccl=/ ) {
# spent 2µs making 1 call to C4::Search::CORE:match
12841400ns while ( !$cclq && $query =~ /(?:^|\W)([\w-]+)(,[\w-]+)*[:=]/g ) {
# spent 400ns making 1 call to C4::Search::CORE:match
1285 my $dx = lc($1);
1286 $cclq = grep { lc($_) eq $dx } @$cclindexes;
1287 }
1288 $query = "ccl=$query" if $cclq;
1289 }
1290
1291# for handling ccl, cql, pqf queries in diagnostic mode, skip the rest of the steps
1292# DIAGNOSTIC ONLY!!
12931400ns if ( $query =~ /^ccl=/ ) {
# spent 400ns making 1 call to C4::Search::CORE:match
1294 my $q=$';
1295 # This is needed otherwise ccl= and &limit won't work together, and
1296 # this happens when selecting a subject on the opac-detail page
1297 @limits = grep {!/^$/} @limits;
1298 if ( @limits ) {
1299 $q .= ' and '.join(' and ', @limits);
1300 }
1301 return ( undef, $q, $q, "q=ccl=".uri_escape($q), $q, '', '', '', '', 'ccl' );
1302 }
13031600ns if ( $query =~ /^cql=/ ) {
# spent 600ns making 1 call to C4::Search::CORE:match
1304 return ( undef, $', $', "q=cql=".uri_escape($'), $', '', '', '', '', 'cql' );
1305 }
13061400ns if ( $query =~ /^pqf=/ ) {
# spent 400ns making 1 call to C4::Search::CORE:match
1307 if ($query_desc) {
1308 $query_cgi = "q=".uri_escape($query_desc);
1309 } else {
1310 $query_desc = $';
1311 $query_cgi = "q=pqf=".uri_escape($');
1312 }
1313 return ( undef, $', $', $query_cgi, $query_desc, '', '', '', '', 'pqf' );
1314 }
1315
1316 # pass nested queries directly
1317 # FIXME: need better handling of some of these variables in this case
1318 # Nested queries aren't handled well and this implementation is flawed and causes users to be
1319 # unable to search for anything containing () commenting out, will be rewritten for 3.4.0
1320# if ( $query =~ /(\(|\))/ ) {
1321# return (
1322# undef, $query, $simple_query, $query_cgi,
1323# $query, $limit, $limit_cgi, $limit_desc,
1324# $stopwords_removed, 'ccl'
1325# );
1326# }
1327
1328# Form-based queries are non-nested and fixed depth, so we can easily modify the incoming
1329# query operands and indexes and add stemming, truncation, field weighting, etc.
1330# Once we do so, we'll end up with a value in $query, just like if we had an
1331# incoming $query from the user
1332 else {
1333 $query = ""
1334 ; # clear it out so we can populate properly with field-weighted, stemmed, etc. query
1335 my $previous_operand
1336 ; # a flag used to keep track if there was a previous query
1337 # if there was, we can apply the current operator
1338 # for every operand
1339 for ( my $i = 0 ; $i <= @operands ; $i++ ) {
1340
1341 # COMBINE OPERANDS, INDEXES AND OPERATORS
1342 if ( $operands[$i] ) {
134319µs $operands[$i]=~s/^\s+//;
# spent 9µs making 1 call to C4::Search::CORE:subst
1344
1345 # A flag to determine whether or not to add the index to the query
1346 my $indexes_set;
1347
1348# If the user is sophisticated enough to specify an index, turn off field weighting, stemming, and stopword handling
13491500ns if ( $operands[$i] =~ /\w(:|=)/ || $scan ) {
# spent 500ns making 1 call to C4::Search::CORE:match
1350 $weight_fields = 0;
1351 $stemming = 0;
1352 $remove_stopwords = 0;
1353 } else {
135413µs $operands[$i] =~ s/\?/{?}/g; # need to escape question marks
# spent 3µs making 1 call to C4::Search::CORE:subst
1355 }
1356 my $operand = $operands[$i];
1357 my $index = $indexes[$i];
1358
1359 # Add index-specific attributes
1360 # Date of Publication
1361 if ( $index eq 'yr' ) {
1362 $index .= ",st-numeric";
1363 $indexes_set++;
1364 $stemming = $auto_truncation = $weight_fields = $fuzzy_enabled = $remove_stopwords = 0;
1365 }
1366
1367 # Date of Acquisition
1368 elsif ( $index eq 'acqdate' ) {
1369 $index .= ",st-date-normalized";
1370 $indexes_set++;
1371 $stemming = $auto_truncation = $weight_fields = $fuzzy_enabled = $remove_stopwords = 0;
1372 }
1373 # ISBN,ISSN,Standard Number, don't need special treatment
1374 elsif ( $index eq 'nb' || $index eq 'ns' ) {
1375 (
1376 $stemming, $auto_truncation,
1377 $weight_fields, $fuzzy_enabled,
1378 $remove_stopwords
1379 ) = ( 0, 0, 0, 0, 0 );
1380
1381 }
1382
1383 if(not $index){
1384 $index = 'kw';
1385 }
1386
1387 # Set default structure attribute (word list)
1388 my $struct_attr = q{};
138914µs unless ( $indexes_set || !$index || $index =~ /(st-|phr|ext|wrdl|nb|ns)/ ) {
# spent 4µs making 1 call to C4::Search::CORE:match
1390 $struct_attr = ",wrdl";
1391 }
1392
1393 # Some helpful index variants
1394 my $index_plus = $index . $struct_attr . ':';
1395 my $index_plus_comma = $index . $struct_attr . ',';
1396
1397 # Remove Stopwords
1398 if ($remove_stopwords) {
1399 ( $operand, $stopwords_removed ) =
1400 _remove_stopwords( $operand, $index );
1401 warn "OPERAND w/out STOPWORDS: >$operand<" if $DEBUG;
1402 warn "REMOVED STOPWORDS: @$stopwords_removed"
1403 if ( $stopwords_removed && $DEBUG );
1404 }
1405
1406 if ($auto_truncation){
1407 unless ( $index =~ /(st-|phr|ext)/ ) {
1408 #FIXME only valid with LTR scripts
1409 $operand=join(" ",map{
1410 (index($_,"*")>0?"$_":"$_*")
1411 }split (/\s+/,$operand));
1412 warn $operand if $DEBUG;
1413 }
1414 }
1415
1416 # Detect Truncation
1417 my $truncated_operand;
1418155µs my( $nontruncated, $righttruncated, $lefttruncated,
# spent 55µs making 1 call to C4::Search::_detect_truncation
1419 $rightlefttruncated, $regexpr
1420 ) = _detect_truncation( $operand, $index );
1421 warn
1422"TRUNCATION: NON:>@$nontruncated< RIGHT:>@$righttruncated< LEFT:>@$lefttruncated< RIGHTLEFT:>@$rightlefttruncated< REGEX:>@$regexpr<"
1423 if $DEBUG;
1424
1425 # Apply Truncation
1426 if (
1427 scalar(@$righttruncated) + scalar(@$lefttruncated) +
1428 scalar(@$rightlefttruncated) > 0 )
1429 {
1430
1431 # Don't field weight or add the index to the query, we do it here
1432 $indexes_set = 1;
1433 undef $weight_fields;
1434 my $previous_truncation_operand;
1435 if (scalar @$nontruncated) {
1436 $truncated_operand .= "$index_plus @$nontruncated ";
1437 $previous_truncation_operand = 1;
1438 }
1439 if (scalar @$righttruncated) {
1440 $truncated_operand .= "and " if $previous_truncation_operand;
1441 $truncated_operand .= $index_plus_comma . "rtrn:@$righttruncated ";
1442 $previous_truncation_operand = 1;
1443 }
1444 if (scalar @$lefttruncated) {
1445 $truncated_operand .= "and " if $previous_truncation_operand;
1446 $truncated_operand .= $index_plus_comma . "ltrn:@$lefttruncated ";
1447 $previous_truncation_operand = 1;
1448 }
1449 if (scalar @$rightlefttruncated) {
1450 $truncated_operand .= "and " if $previous_truncation_operand;
1451 $truncated_operand .= $index_plus_comma . "rltrn:@$rightlefttruncated ";
1452 $previous_truncation_operand = 1;
1453 }
1454 }
1455 $operand = $truncated_operand if $truncated_operand;
1456 warn "TRUNCATED OPERAND: >$truncated_operand<" if $DEBUG;
1457
1458 # Handle Stemming
1459 my $stemmed_operand;
1460 $stemmed_operand = _build_stemmed_operand($operand, $lang)
1461 if $stemming;
1462
1463 warn "STEMMED OPERAND: >$stemmed_operand<" if $DEBUG;
1464
1465 # Handle Field Weighting
1466 my $weighted_operand;
1467 if ($weight_fields) {
1468144µs $weighted_operand = _build_weighted_query( $operand, $stemmed_operand, $index );
# spent 44µs making 1 call to C4::Search::_build_weighted_query
1469 $operand = $weighted_operand;
1470 $indexes_set = 1;
1471 }
1472
1473 warn "FIELD WEIGHTED OPERAND: >$weighted_operand<" if $DEBUG;
1474
1475 # If there's a previous operand, we need to add an operator
1476 if ($previous_operand) {
1477
1478 # User-specified operator
1479 if ( $operators[ $i - 1 ] ) {
1480 $query .= " $operators[$i-1] ";
1481 $query .= " $index_plus " unless $indexes_set;
1482 $query .= " $operand";
1483 $query_cgi .= "&op=".uri_escape($operators[$i-1]);
1484 $query_cgi .= "&idx=".uri_escape($index) if $index;
1485 $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i];
1486 $query_desc .=
1487 " $operators[$i-1] $index_plus $operands[$i]";
1488 }
1489
1490 # Default operator is and
1491 else {
1492 $query .= " and ";
1493 $query .= "$index_plus " unless $indexes_set;
1494 $query .= "$operand";
1495 $query_cgi .= "&op=and&idx=".uri_escape($index) if $index;
1496 $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i];
1497 $query_desc .= " and $index_plus $operands[$i]";
1498 }
1499 }
1500
1501 # There isn't a pervious operand, don't need an operator
1502 else {
1503
1504 # Field-weighted queries already have indexes set
1505 $query .= " $index_plus " unless $indexes_set;
1506 $query .= $operand;
1507 $query_desc .= " $index_plus $operands[$i]";
1508156µs $query_cgi .= "&idx=".uri_escape($index) if $index;
# spent 56µs making 1 call to URI::Escape::uri_escape
150918µs $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i];
# spent 8µs making 1 call to URI::Escape::uri_escape
1510 $previous_operand = 1;
1511 }
1512 } #/if $operands
1513 } # /for
1514 }
1515 warn "QUERY BEFORE LIMITS: >$query<" if $DEBUG;
1516
1517 # add limits
1518 my %group_OR_limits;
1519 my $availability_limit;
1520 foreach my $this_limit (@limits) {
1521 next unless $this_limit;
1522 if ( $this_limit =~ /available/ ) {
1523#
1524## 'available' is defined as (items.onloan is NULL) and (items.itemlost = 0)
1525## In English:
1526## all records not indexed in the onloan register (zebra) and all records with a value of lost equal to 0
1527 $availability_limit .=
1528"( ( allrecords,AlwaysMatches='' not onloan,AlwaysMatches='') and (lost,st-numeric=0) )"; #or ( allrecords,AlwaysMatches='' not lost,AlwaysMatches='')) )";
1529 $limit_cgi .= "&limit=available";
1530 $limit_desc .= "";
1531 }
1532
1533 # group_OR_limits, prefixed by mc-
1534 # OR every member of the group
1535 elsif ( $this_limit =~ /mc/ ) {
1536 my ($k,$v) = split(/:/, $this_limit,2);
1537 if ( $k !~ /mc-i(tem)?type/ ) {
1538 # in case the mc-ccode value has complicating chars like ()'s inside it we wrap in quotes
1539 $this_limit =~ tr/"//d;
1540 $this_limit = $k.":\"".$v."\"";
1541 }
1542
1543 $group_OR_limits{$k} .= " or " if $group_OR_limits{$k};
1544 $limit_desc .= " or " if $group_OR_limits{$k};
1545 $group_OR_limits{$k} .= "$this_limit";
1546 $limit_cgi .= "&limit=$this_limit";
1547 $limit_desc .= " $this_limit";
1548 }
1549
1550 # Regular old limits
1551 else {
1552 $limit .= " and " if $limit || $query;
1553 $limit .= "$this_limit";
1554 $limit_cgi .= "&limit=$this_limit";
1555 if ($this_limit =~ /^branch:(.+)/) {
1556 my $branchcode = $1;
1557 my $branchname = GetBranchName($branchcode);
1558 if (defined $branchname) {
1559 $limit_desc .= " branch:$branchname";
1560 } else {
1561 $limit_desc .= " $this_limit";
1562 }
1563 } else {
1564 $limit_desc .= " $this_limit";
1565 }
1566 }
1567 }
1568 foreach my $k (keys (%group_OR_limits)) {
1569 $limit .= " and " if ( $query || $limit );
1570 $limit .= "($group_OR_limits{$k})";
1571 }
1572 if ($availability_limit) {
1573 $limit .= " and " if ( $query || $limit );
1574 $limit .= "($availability_limit)";
1575 }
1576
1577 # Normalize the query and limit strings
1578 # This is flawed , means we can't search anything with : in it
1579 # if user wants to do ccl or cql, start the query with that
1580# $query =~ s/:/=/g;
158112µs $query =~ s/(?<=(ti|au|pb|su|an|kw|mc|nb|ns)):/=/g;
# spent 2µs making 1 call to C4::Search::CORE:subst
158211µs $query =~ s/(?<=(wrdl)):/=/g;
# spent 1µs making 1 call to C4::Search::CORE:subst
15831600ns $query =~ s/(?<=(trn|phr)):/=/g;
# spent 600ns making 1 call to C4::Search::CORE:subst
15841900ns $limit =~ s/:/=/g;
# spent 900ns making 1 call to C4::Search::CORE:subst
1585 for ( $query, $query_desc, $limit, $limit_desc ) {
158642µs s/ +/ /g; # remove extra spaces
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 550ns/call
158747µs s/^ //g; # remove any beginning spaces
# spent 7µs making 4 calls to C4::Search::CORE:subst, avg 2µs/call
158842µs s/ $//g; # remove any ending spaces
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 375ns/call
158942µs s/==/=/g; # remove double == from query
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 400ns/call
1590 }
159112µs $query_cgi =~ s/^&//; # remove unnecessary & from beginning of the query cgi
# spent 2µs making 1 call to C4::Search::CORE:subst
1592
1593 for ($query_cgi,$simple_query) {
159421µs s/"//g;
# spent 1µs making 2 calls to C4::Search::CORE:subst, avg 500ns/call
1595 }
1596 # append the limit to the query
1597 $query .= " " . $limit;
1598
1599 # Warnings if DEBUG
1600 if ($DEBUG) {
1601 warn "QUERY:" . $query;
1602 warn "QUERY CGI:" . $query_cgi;
1603 warn "QUERY DESC:" . $query_desc;
1604 warn "LIMIT:" . $limit;
1605 warn "LIMIT CGI:" . $limit_cgi;
1606 warn "LIMIT DESC:" . $limit_desc;
1607 warn "---------\nLeave buildQuery\n---------";
1608 }
1609 return (
1610 undef, $query, $simple_query, $query_cgi,
1611 $query_desc, $limit, $limit_cgi, $limit_desc,
1612 $stopwords_removed, $query_type
1613 );
1614}
1615
1616=head2 searchResults
1617
- -
1626# IMO this subroutine is pretty messy still -- it's responsible for
1627# building the HTML output for the template
1628
# spent 4.40s (27.4ms+4.37) within C4::Search::searchResults which was called: # once (27.4ms+4.37s) by main::RUNTIME at line 560 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub searchResults {
1629442934.2ms my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults ) = @_;
163011.02ms my $dbh = C4::Context->dbh;
# spent 1.02ms making 1 call to C4::Context::dbh
1631 my @newresults;
1632
1633 require C4::Items;
1634
1635 $search_context = 'opac' if !$search_context || $search_context ne 'intranet';
1636 my ($is_opac, $hidelostitems);
1637 if ($search_context eq 'opac') {
1638111µs $hidelostitems = C4::Context->preference('hidelostitems');
# spent 11µs making 1 call to C4::Context::preference
1639 $is_opac = 1;
1640 }
1641
1642 #Build branchnames hash
1643 #find branchname
1644 #get branch information.....
1645 my %branches;
164619µs2195µs my $bsth =$dbh->prepare("SELECT branchcode,branchname FROM branches"); # FIXME : use C4::Branch::GetBranches
# spent 105µs making 1 call to DBI::db::prepare # spent 90µs making 1 call to DBD::mysql::db::prepare
164711.69ms $bsth->execute();
# spent 1.69ms making 1 call to DBI::st::execute
164833280µs while ( my $bdata = $bsth->fetchrow_hashref ) {
# spent 197µs making 11 calls to DBI::st::fetchrow_hashref, avg 18µs/call # spent 42µs making 11 calls to DBI::st::fetch, avg 4µs/call # spent 40µs making 11 calls to DBI::common::FETCH, avg 4µs/call
1649 $branches{ $bdata->{'branchcode'} } = $bdata->{'branchname'};
1650 }
1651# FIXME - We build an authorised values hash here, using the default framework
1652# though it is possible to have different authvals for different fws.
1653
165444.59ms my $shelflocations =GetKohaAuthorisedValues('items.location','');
# spent 4.59ms making 1 call to C4::Koha::GetKohaAuthorisedValues # spent 7µs making 2 calls to DBI::common::DESTROY, avg 4µs/call # spent 2µs making 1 call to DBD::_mem::common::DESTROY
1655
1656 # get notforloan authorised value list (see $shelflocations FIXME)
165742.13ms my $notforloan_authorised_value = GetAuthValCode('items.notforloan','');
# spent 2.12ms making 1 call to C4::Koha::GetAuthValCode # spent 9µs making 2 calls to DBI::common::DESTROY, avg 4µs/call # spent 2µs making 1 call to DBD::_mem::common::DESTROY
1658
1659 #Build itemtype hash
1660 #find itemtype & itemtype image
1661 my %itemtypes;
1662131µs2140µs $bsth =
# spent 76µs making 1 call to DBI::db::prepare # spent 65µs making 1 call to DBD::mysql::db::prepare
1663 $dbh->prepare(
1664 "SELECT itemtype,description,imageurl,summary,notforloan FROM itemtypes"
1665 );
166641.03ms $bsth->execute();
# spent 1.02ms making 1 call to DBI::st::execute # spent 7µs making 2 calls to DBI::common::DESTROY, avg 4µs/call # spent 3µs making 1 call to DBD::_mem::common::DESTROY
1667721.39ms while ( my $bdata = $bsth->fetchrow_hashref ) {
# spent 1.06ms making 24 calls to DBI::st::fetchrow_hashref, avg 44µs/call # spent 174µs making 24 calls to DBI::st::fetch, avg 7µs/call # spent 154µs making 24 calls to DBI::common::FETCH, avg 6µs/call
1668 foreach (qw(description imageurl summary notforloan)) {
1669 $itemtypes{ $bdata->{'itemtype'} }->{$_} = $bdata->{$_};
1670 }
1671 }
1672
1673 #search item field code
167415.92ms my ($itemtag, undef) = &GetMarcFromKohaField( "items.itemnumber", "" );
# spent 5.92ms making 1 call to C4::Biblio::GetMarcFromKohaField
1675
1676 ## find column names of items related to MARC
1677110µs2236µs my $sth2 = $dbh->prepare("SHOW COLUMNS FROM items");
# spent 128µs making 1 call to DBI::db::prepare # spent 108µs making 1 call to DBD::mysql::db::prepare
167814.60ms $sth2->execute;
# spent 4.60ms making 1 call to DBI::st::execute
1679 my %subfieldstosearch;
168041268µs while ( ( my $column ) = $sth2->fetchrow ) {
# spent 268µs making 41 calls to DBI::st::fetchrow, avg 7µs/call
168140602µs my ( $tagfield, $tagsubfield ) =
# spent 602µs making 40 calls to C4::Biblio::GetMarcFromKohaField, avg 15µs/call
1682 &GetMarcFromKohaField( "items." . $column, "" );
1683 $subfieldstosearch{$column} = $tagsubfield;
1684 }
1685
1686 # handle which records to actually retrieve
1687 my $times;
1688 if ( $hits && $offset + $results_per_page <= $hits ) {
1689 $times = $offset + $results_per_page;
1690 }
1691 else {
1692 $times = $hits; # FIXME: if $hits is undefined, why do we want to equal it?
1693 }
1694
1695111µs my $marcflavour = C4::Context->preference("marcflavour");
# spent 11µs making 1 call to C4::Context::preference
1696 # We get the biblionumber position in MARC
1697111µs my ($bibliotag,$bibliosubf)=GetMarcFromKohaField('biblio.biblionumber','');
# spent 11µs making 1 call to C4::Biblio::GetMarcFromKohaField
1698
1699 # loop through all of the records we've retrieved
1700 for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) {
170125138ms my $marcrecord = MARC::File::USMARC::decode( $marcresults->[$i] );
# spent 138ms making 25 calls to MARC::File::USMARC::decode, avg 5.51ms/call
170212576.2ms my $fw = $scan
# spent 67.9ms making 25 calls to C4::Biblio::GetFrameworkCode, avg 2.72ms/call # spent 7.83ms making 25 calls to MARC::Record::subfield, avg 313µs/call # spent 385µs making 50 calls to DBI::common::DESTROY, avg 8µs/call # spent 102µs making 25 calls to DBD::_mem::common::DESTROY, avg 4µs/call
1703 ? undef
1704 : $bibliotag < 10
1705 ? GetFrameworkCode($marcrecord->field($bibliotag)->data)
1706 : GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf));
17072547.9ms my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, $fw );
# spent 47.9ms making 25 calls to C4::Biblio::TransformMarcToKoha, avg 1.91ms/call
170810057.7ms $oldbiblio->{subtitle} = GetRecordValue('subtitle', $marcrecord, $fw);
# spent 57.4ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.30ms/call # spent 250µs making 50 calls to DBI::common::DESTROY, avg 5µs/call # spent 76µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call
170910055.9ms $oldbiblio->{source_t} = GetRecordValue('source_t', $marcrecord, $fw);
# spent 55.5ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.22ms/call # spent 260µs making 50 calls to DBI::common::DESTROY, avg 5µs/call # spent 66µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call
171010053.1ms $oldbiblio->{source_g} = GetRecordValue('source_g', $marcrecord, $fw);
# spent 52.8ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.11ms/call # spent 229µs making 50 calls to DBI::common::DESTROY, avg 5µs/call # spent 63µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call
1711 $oldbiblio->{result_number} = $i + 1;
1712
1713 # add imageurl to itemtype if there is one
1714252.03ms $oldbiblio->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} );
# spent 2.03ms making 25 calls to C4::Koha::getitemtypeimagelocation, avg 81µs/call
1715
171625134µs $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 ) ) : [];
# spent 134µs making 25 calls to C4::Context::preference, avg 5µs/call
1717259.42ms $oldbiblio->{normalized_upc} = GetNormalizedUPC( $marcrecord,$marcflavour);
# spent 9.42ms making 25 calls to C4::Koha::GetNormalizedUPC, avg 377µs/call
1718257.21ms $oldbiblio->{normalized_ean} = GetNormalizedEAN( $marcrecord,$marcflavour);
# spent 7.21ms making 25 calls to C4::Koha::GetNormalizedEAN, avg 289µs/call
1719257.71ms $oldbiblio->{normalized_oclc} = GetNormalizedOCLCNumber($marcrecord,$marcflavour);
# spent 7.71ms making 25 calls to C4::Koha::GetNormalizedOCLCNumber, avg 308µs/call
1720256.83ms $oldbiblio->{normalized_isbn} = GetNormalizedISBN(undef,$marcrecord,$marcflavour);
# spent 6.83ms making 25 calls to C4::Koha::GetNormalizedISBN, avg 273µs/call
1721 $oldbiblio->{content_identifier_exists} = 1 if ($oldbiblio->{normalized_isbn} or $oldbiblio->{normalized_oclc} or $oldbiblio->{normalized_ean} or $oldbiblio->{normalized_upc});
1722
1723 # edition information, if any
1724 $oldbiblio->{edition} = $oldbiblio->{editionstatement};
1725 $oldbiblio->{description} = $itemtypes{ $oldbiblio->{itemtype} }->{description};
1726 # Build summary if there is one (the summary is defined in the itemtypes table)
1727 # FIXME: is this used anywhere, I think it can be commented out? -- JF
1728 if ( $itemtypes{ $oldbiblio->{itemtype} }->{summary} ) {
1729 my $summary = $itemtypes{ $oldbiblio->{itemtype} }->{summary};
1730 my @fields = $marcrecord->fields();
1731
1732 my $newsummary;
1733 foreach my $line ( "$summary\n" =~ /(.*)\n/g ){
1734 my $tags = {};
1735 foreach my $tag ( $line =~ /\[(\d{3}[\w|\d])\]/ ) {
1736 $tag =~ /(.{3})(.)/;
1737 if($marcrecord->field($1)){
1738 my @abc = $marcrecord->field($1)->subfield($2);
1739 $tags->{$tag} = $#abc + 1 ;
1740 }
1741 }
1742
1743 # We catch how many times to repeat this line
1744 my $max = 0;
1745 foreach my $tag (keys(%$tags)){
1746 $max = $tags->{$tag} if($tags->{$tag} > $max);
1747 }
1748
1749 # we replace, and repeat each line
1750 for (my $i = 0 ; $i < $max ; $i++){
1751 my $newline = $line;
1752
1753 foreach my $tag ( $newline =~ /\[(\d{3}[\w|\d])\]/g ) {
1754 $tag =~ /(.{3})(.)/;
1755
1756 if($marcrecord->field($1)){
1757 my @repl = $marcrecord->field($1)->subfield($2);
1758 my $subfieldvalue = $repl[$i];
1759
1760 if (! utf8::is_utf8($subfieldvalue)) {
1761 utf8::decode($subfieldvalue);
1762 }
1763
1764 $newline =~ s/\[$tag\]/$subfieldvalue/g;
1765 }
1766 }
1767 $newsummary .= "$newline\n";
1768 }
1769 }
1770
1771 $newsummary =~ s/\[(.*?)]//g;
1772 $newsummary =~ s/\n/<br\/>/g;
1773 $oldbiblio->{summary} = $newsummary;
1774 }
1775
1776 # Pull out the items fields
1777256.26ms my @fields = $marcrecord->field($itemtag);
# spent 6.26ms making 25 calls to MARC::Record::field, avg 250µs/call
177825200µs my $marcflavor = C4::Context->preference("marcflavour");
# spent 200µs making 25 calls to C4::Context::preference, avg 8µs/call
1779 # adding linked items that belong to host records
1780 my $analyticsfield = '773';
1781 if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
1782 $analyticsfield = '773';
1783 } elsif ($marcflavor eq 'UNIMARC') {
1784 $analyticsfield = '461';
1785 }
1786256.14ms foreach my $hostfield ( $marcrecord->field($analyticsfield)) {
# spent 6.14ms making 25 calls to MARC::Record::field, avg 246µs/call
17879180µs my $hostbiblionumber = $hostfield->subfield("0");
# spent 180µs making 9 calls to MARC::Field::subfield, avg 20µs/call
17889113µs my $linkeditemnumber = $hostfield->subfield("9");
# spent 113µs making 9 calls to MARC::Field::subfield, avg 13µs/call
1789 if(!$hostbiblionumber eq undef){
1790 my $hostbiblio = GetMarcBiblio($hostbiblionumber, 1);
1791 my ($itemfield, undef) = GetMarcFromKohaField( 'items.itemnumber', GetFrameworkCode($hostbiblionumber) );
1792 if(!$hostbiblio eq undef){
1793 my @hostitems = $hostbiblio->field($itemfield);
1794 foreach my $hostitem (@hostitems){
1795 if ($hostitem->subfield("9") eq $linkeditemnumber){
1796 my $linkeditem =$hostitem;
1797 # append linked items if they exist
1798 if (!$linkeditem eq undef){
1799 push (@fields, $linkeditem);}
1800 }
1801 }
1802 }
1803 }
1804 }
1805
1806 # Setting item statuses for display
1807 my @available_items_loop;
1808 my @onloan_items_loop;
1809 my @other_items_loop;
1810
1811 my $available_items;
1812 my $onloan_items;
1813 my $other_items;
1814
1815 my $ordered_count = 0;
1816 my $available_count = 0;
1817 my $onloan_count = 0;
1818 my $longoverdue_count = 0;
1819 my $other_count = 0;
1820 my $wthdrawn_count = 0;
1821 my $itemlost_count = 0;
1822 my $hideatopac_count = 0;
1823 my $itembinding_count = 0;
1824 my $itemdamaged_count = 0;
1825 my $item_in_transit_count = 0;
1826 my $can_place_holds = 0;
1827 my $item_onhold_count = 0;
1828 my $items_count = scalar(@fields);
1829252.17ms my $maxitems_pref = C4::Context->preference('maxItemsinSearchResults');
# spent 2.17ms making 25 calls to C4::Context::preference, avg 87µs/call
1830 my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1;
1831 my @hiddenitems; # hidden itemnumbers based on OpacHiddenItems syspref
1832
1833 # loop through every item
1834 foreach my $field (@fields) {
1835 my $item;
1836
1837 # populate the items hash
1838 foreach my $code ( keys %subfieldstosearch ) {
1839128071.0ms $item->{$code} = $field->subfield( $subfieldstosearch{$code} );
# spent 71.0ms making 1280 calls to MARC::Field::subfield, avg 55µs/call
1840 }
1841 $item->{description} = $itemtypes{ $item->{itype} }{description};
1842
1843 # OPAC hidden items
1844 if ($is_opac) {
1845 # hidden because lost
1846 if ($hidelostitems && $item->{itemlost}) {
1847 $hideatopac_count++;
1848 next;
1849 }
1850 # hidden based on OpacHiddenItems syspref
185132122ms my @hi = C4::Items::GetHiddenItemnumbers($item);
# spent 122ms making 32 calls to C4::Items::GetHiddenItemnumbers, avg 3.81ms/call
1852 if (scalar @hi) {
1853 push @hiddenitems, @hi;
1854 $hideatopac_count++;
1855 next;
1856 }
1857 }
1858
1859322.46ms my $hbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'homebranch' : 'holdingbranch';
# spent 2.46ms making 32 calls to C4::Context::preference, avg 77µs/call
186032104µs my $otherbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'holdingbranch' : 'homebranch';
# spent 104µs making 32 calls to C4::Context::preference, avg 3µs/call
1861
1862 # set item's branch name, use HomeOrHoldingBranch syspref first, fall back to the other one
1863 if ($item->{$hbranch}) {
1864 $item->{'branchname'} = $branches{$item->{$hbranch}};
1865 }
1866 elsif ($item->{$otherbranch}) { # Last resort
1867 $item->{'branchname'} = $branches{$item->{$otherbranch}};
1868 }
1869
1870 my $prefix = $item->{$hbranch} . '--' . $item->{location} . $item->{itype} . $item->{itemcallnumber};
1871# For each grouping of items (onloan, available, unavailable), we build a key to store relevant info about that item
187232368µs my $userenv = C4::Context->userenv;
# spent 368µs making 32 calls to C4::Context::userenv, avg 11µs/call
1873 if ( $item->{onloan} && !(C4::Members::GetHideLostItemsPreference($userenv->{'number'}) && $item->{itemlost}) ) {
1874 $onloan_count++;
1875 my $key = $prefix . $item->{onloan} . $item->{barcode};
1876 $onloan_items->{$key}->{due_date} = format_date($item->{onloan});
1877 $onloan_items->{$key}->{count}++ if $item->{$hbranch};
1878 $onloan_items->{$key}->{branchname} = $item->{branchname};
1879 $onloan_items->{$key}->{location} = $shelflocations->{ $item->{location} };
1880 $onloan_items->{$key}->{itemcallnumber} = $item->{itemcallnumber};
1881 $onloan_items->{$key}->{description} = $item->{description};
1882 $onloan_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
1883 # if something's checked out and lost, mark it as 'long overdue'
1884 if ( $item->{itemlost} ) {
1885 $onloan_items->{$prefix}->{longoverdue}++;
1886 $longoverdue_count++;
1887 } else { # can place holds as long as item isn't lost
1888 $can_place_holds = 1;
1889 }
1890 }
1891
1892 # items not on loan, but still unavailable ( lost, withdrawn, damaged )
1893 else {
1894
1895 # item is on order
1896 if ( $item->{notforloan} < 0 ) {
1897 $ordered_count++;
1898 }
1899
1900 # is item in transit?
1901 my $transfertwhen = '';
1902 my ($transfertfrom, $transfertto);
1903
1904 # is item on the reserve shelf?
1905 my $reservestatus = '';
1906
1907 unless ($item->{wthdrawn}
1908 || $item->{itemlost}
1909 || $item->{damaged}
1910 || $item->{notforloan}
1911 || $items_count > 20) {
1912
1913 # A couple heuristics to limit how many times
1914 # we query the database for item transfer information, sacrificing
1915 # accuracy in some cases for speed;
1916 #
1917 # 1. don't query if item has one of the other statuses
1918 # 2. don't check transit status if the bib has
1919 # more than 20 items
1920 #
1921 # FIXME: to avoid having the query the database like this, and to make
1922 # the in transit status count as unavailable for search limiting,
1923 # should map transit status to record indexed in Zebra.
1924 #
192512881.0ms ($transfertwhen, $transfertfrom, $transfertto) = C4::Circulation::GetTransfers($item->{itemnumber});
# spent 80.5ms making 32 calls to C4::Circulation::GetTransfers, avg 2.52ms/call # spent 389µs making 64 calls to DBI::common::DESTROY, avg 6µs/call # spent 113µs making 32 calls to DBD::_mem::common::DESTROY, avg 4µs/call
1926128122ms $reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber}, $oldbiblio->{biblionumber} );
# spent 122ms making 32 calls to C4::Reserves::GetReserveStatus, avg 3.80ms/call # spent 400µs making 64 calls to DBI::common::DESTROY, avg 6µs/call # spent 79µs making 32 calls to DBD::_mem::common::DESTROY, avg 2µs/call
1927 }
1928
1929 # item is withdrawn, lost, damaged, not for loan, reserved or in transit
1930 if ( $item->{wthdrawn}
1931 || $item->{itemlost}
1932 || $item->{damaged}
1933 || $item->{notforloan}
1934 || $reservestatus eq 'Waiting'
1935 || ($transfertwhen ne ''))
1936 {
1937 $wthdrawn_count++ if $item->{wthdrawn};
1938 $itemlost_count++ if $item->{itemlost};
1939 $itemdamaged_count++ if $item->{damaged};
1940 $item_in_transit_count++ if $transfertwhen ne '';
1941 $item_onhold_count++ if $reservestatus eq 'Waiting';
1942 $item->{status} = $item->{wthdrawn} . "-" . $item->{itemlost} . "-" . $item->{damaged} . "-" . $item->{notforloan};
1943
1944 # can place hold on item ?
1945 if ( !$item->{itemlost} ) {
1946 if ( !$item->{wthdrawn} ){
1947 if ( $item->{damaged} ){
1948 if ( C4::Context->preference('AllowHoldsOnDamagedItems') ){
1949 # can place a hold on a damaged item if AllowHoldsOnDamagedItems is true
1950 if ( ( !$item->{notforloan} || $item->{notforloan} < 0 ) ){
1951 # item is either for loan or has notforloan < 0
1952 $can_place_holds = 1;
1953 }
1954 }
1955 } elsif ( $item->{notforloan} < 0 ) {
1956 # item is not damaged and notforloan is < 0
1957 $can_place_holds = 1;
1958 }
1959 }
1960 }
1961
1962 $other_count++;
1963
1964 my $key = $prefix . $item->{status};
1965 foreach (qw(wthdrawn itemlost damaged branchname itemcallnumber)) {
1966 $other_items->{$key}->{$_} = $item->{$_};
1967 }
1968 $other_items->{$key}->{intransit} = ( $transfertwhen ne '' ) ? 1 : 0;
1969 $other_items->{$key}->{onhold} = ($reservestatus) ? 1 : 0;
1970 $other_items->{$key}->{notforloan} = GetAuthorisedValueDesc('','',$item->{notforloan},'','',$notforloan_authorised_value) if $notforloan_authorised_value and $item->{notforloan};
1971 $other_items->{$key}->{count}++ if $item->{$hbranch};
1972 $other_items->{$key}->{location} = $shelflocations->{ $item->{location} };
1973 $other_items->{$key}->{description} = $item->{description};
1974 $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
1975 }
1976 # item is available
1977 else {
1978 $can_place_holds = 1;
1979 $available_count++;
1980 $available_items->{$prefix}->{count}++ if $item->{$hbranch};
1981 foreach (qw(branchname itemcallnumber description)) {
1982 $available_items->{$prefix}->{$_} = $item->{$_};
1983 }
1984 $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} };
1985322.60ms $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
# spent 2.60ms making 32 calls to C4::Koha::getitemtypeimagelocation, avg 81µs/call
1986 }
1987 }
1988 } # notforloan, item level and biblioitem level
1989
1990 # if all items are hidden, do not show the record
1991 if ($items_count > 0 && $hideatopac_count == $items_count) {
1992 next;
1993 }
1994
1995 my ( $availableitemscount, $onloanitemscount, $otheritemscount );
199625221µs for my $key ( sort keys %$onloan_items ) {
# spent 221µs making 25 calls to C4::Search::CORE:sort, avg 9µs/call
1997 (++$onloanitemscount > $maxitems) and last;
1998 push @onloan_items_loop, $onloan_items->{$key};
1999 }
20002527µs for my $key ( sort keys %$other_items ) {
# spent 27µs making 25 calls to C4::Search::CORE:sort, avg 1µs/call
2001 (++$otheritemscount > $maxitems) and last;
2002 push @other_items_loop, $other_items->{$key};
2003 }
20042535µs for my $key ( sort keys %$available_items ) {
# spent 35µs making 25 calls to C4::Search::CORE:sort, avg 1µs/call
2005 (++$availableitemscount > $maxitems) and last;
2006 push @available_items_loop, $available_items->{$key}
2007 }
2008
2009 # XSLT processing of some stuff
201032.26ms2327µs
# spent 178µs (28+149) within C4::Search::BEGIN@2010 which was called: # once (28µs+149µs) by main::BEGIN@48 at line 2010
use C4::Charset;
# spent 178µs making 1 call to C4::Search::BEGIN@2010 # spent 149µs making 1 call to Exporter::import
201125112ms SetUTF8Flag($marcrecord);
# spent 112ms making 25 calls to C4::Charset::SetUTF8Flag, avg 4.48ms/call
2012 warn $marcrecord->as_formatted if $DEBUG;
2013 my $interface = $search_context eq 'opac' ? 'OPAC' : '';
20141003.36s if (!$scan && C4::Context->preference($interface . "XSLTResultsDisplay")) {
# spent 3.36s making 25 calls to C4::XSLT::XSLTParse4Display, avg 134ms/call # spent 1.38ms making 50 calls to XML::LibXML::Node::DESTROY, avg 28µs/call # spent 338µs making 25 calls to C4::Context::preference, avg 14µs/call
2015 $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, $interface."XSLTResultsDisplay", 1, \@hiddenitems);
2016 # the last parameter tells Koha to clean up the problematic ampersand entities that Zebra outputs
2017 }
2018
2019 # if biblio level itypes are used and itemtype is notforloan, it can't be reserved either
202025225µs if (!C4::Context->preference("item-level_itypes")) {
# spent 225µs making 25 calls to C4::Context::preference, avg 9µs/call
2021 if ($itemtypes{ $oldbiblio->{itemtype} }->{notforloan}) {
2022 $can_place_holds = 0;
2023 }
2024 }
2025 $oldbiblio->{norequests} = 1 unless $can_place_holds;
2026 $oldbiblio->{itemsplural} = 1 if $items_count > 1;
2027 $oldbiblio->{items_count} = $items_count;
2028 $oldbiblio->{available_items_loop} = \@available_items_loop;
2029 $oldbiblio->{onloan_items_loop} = \@onloan_items_loop;
2030 $oldbiblio->{other_items_loop} = \@other_items_loop;
2031 $oldbiblio->{availablecount} = $available_count;
2032 $oldbiblio->{availableplural} = 1 if $available_count > 1;
2033 $oldbiblio->{onloancount} = $onloan_count;
2034 $oldbiblio->{onloanplural} = 1 if $onloan_count > 1;
2035 $oldbiblio->{othercount} = $other_count;
2036 $oldbiblio->{otherplural} = 1 if $other_count > 1;
2037 $oldbiblio->{wthdrawncount} = $wthdrawn_count;
2038 $oldbiblio->{itemlostcount} = $itemlost_count;
2039 $oldbiblio->{damagedcount} = $itemdamaged_count;
2040 $oldbiblio->{intransitcount} = $item_in_transit_count;
2041 $oldbiblio->{onholdcount} = $item_onhold_count;
2042 $oldbiblio->{orderedcount} = $ordered_count;
2043
204425118µs if (C4::Context->preference("AlternateHoldingsField") && $items_count == 0) {
# spent 118µs making 25 calls to C4::Context::preference, avg 5µs/call
2045 my $fieldspec = C4::Context->preference("AlternateHoldingsField");
2046 my $subfields = substr $fieldspec, 3;
2047 my $holdingsep = C4::Context->preference("AlternateHoldingsSeparator") || ' ';
2048 my @alternateholdingsinfo = ();
2049 my @holdingsfields = $marcrecord->field(substr $fieldspec, 0, 3);
2050 my $alternateholdingscount = 0;
2051
2052 for my $field (@holdingsfields) {
2053 my %holding = ( holding => '' );
2054 my $havesubfield = 0;
2055 for my $subfield ($field->subfields()) {
2056 if ((index $subfields, $$subfield[0]) >= 0) {
2057 $holding{'holding'} .= $holdingsep if (length $holding{'holding'} > 0);
2058 $holding{'holding'} .= $$subfield[1];
2059 $havesubfield++;
2060 }
2061 }
2062 if ($havesubfield) {
2063 push(@alternateholdingsinfo, \%holding);
2064 $alternateholdingscount++;
2065 }
2066 }
2067
2068 $oldbiblio->{'ALTERNATEHOLDINGS'} = \@alternateholdingsinfo;
2069 $oldbiblio->{'alternateholdings_count'} = $alternateholdingscount;
2070 }
2071
2072 push( @newresults, $oldbiblio );
2073 }
2074
2075 return @newresults;
2076}
2077
2078=head2 SearchAcquisitions
2079
- -
2082sub SearchAcquisitions{
2083 my ($datebegin, $dateend, $itemtypes,$criteria, $orderby) = @_;
2084
2085 my $dbh=C4::Context->dbh;
2086 # Variable initialization
2087 my $str=qq|
2088 SELECT marcxml
2089 FROM biblio
2090 LEFT JOIN biblioitems ON biblioitems.biblionumber=biblio.biblionumber
2091 LEFT JOIN items ON items.biblionumber=biblio.biblionumber
2092 WHERE dateaccessioned BETWEEN ? AND ?
2093 |;
2094
2095 my (@params,@loopcriteria);
2096
2097 push @params, $datebegin->output("iso");
2098 push @params, $dateend->output("iso");
2099
2100 if (scalar(@$itemtypes)>0 and $criteria ne "itemtype" ){
2101 if(C4::Context->preference("item-level_itypes")){
2102 $str .= "AND items.itype IN (?".( ',?' x scalar @$itemtypes - 1 ).") ";
2103 }else{
2104 $str .= "AND biblioitems.itemtype IN (?".( ',?' x scalar @$itemtypes - 1 ).") ";
2105 }
2106 push @params, @$itemtypes;
2107 }
2108
2109 if ($criteria =~/itemtype/){
2110 if(C4::Context->preference("item-level_itypes")){
2111 $str .= "AND items.itype=? ";
2112 }else{
2113 $str .= "AND biblioitems.itemtype=? ";
2114 }
2115
2116 if(scalar(@$itemtypes) == 0){
2117 my $itypes = GetItemTypes();
2118 for my $key (keys %$itypes){
2119 push @$itemtypes, $key;
2120 }
2121 }
2122
2123 @loopcriteria= @$itemtypes;
2124 }elsif ($criteria=~/itemcallnumber/){
2125 $str .= "AND (items.itemcallnumber LIKE CONCAT(?,'%')
2126 OR items.itemcallnumber is NULL
2127 OR items.itemcallnumber = '')";
2128
2129 @loopcriteria = ("AA".."ZZ", "") unless (scalar(@loopcriteria)>0);
2130 }else {
2131 $str .= "AND biblio.title LIKE CONCAT(?,'%') ";
2132 @loopcriteria = ("A".."z") unless (scalar(@loopcriteria)>0);
2133 }
2134
2135 if ($orderby =~ /date_desc/){
2136 $str.=" ORDER BY dateaccessioned DESC";
2137 } else {
2138 $str.=" ORDER BY title";
2139 }
2140
2141 my $qdataacquisitions=$dbh->prepare($str);
2142
2143 my @loopacquisitions;
2144 foreach my $value(@loopcriteria){
2145 push @params,$value;
2146 my %cell;
2147 $cell{"title"}=$value;
2148 $cell{"titlecode"}=$value;
2149
2150 eval{$qdataacquisitions->execute(@params);};
2151
2152 if ($@){ warn "recentacquisitions Error :$@";}
2153 else {
2154 my @loopdata;
2155 while (my $data=$qdataacquisitions->fetchrow_hashref){
2156 push @loopdata, {"summary"=>GetBiblioSummary( $data->{'marcxml'} ) };
2157 }
2158 $cell{"loopdata"}=\@loopdata;
2159 }
2160 push @loopacquisitions,\%cell if (scalar(@{$cell{loopdata}})>0);
2161 pop @params;
2162 }
2163 $qdataacquisitions->finish;
2164 return \@loopacquisitions;
2165}
2166
2167=head2 enabled_staff_search_views
2168
- -
2191sub enabled_staff_search_views
2192{
2193 return (
2194 can_view_MARC => C4::Context->preference('viewMARC'), # 1 if the staff search allows the MARC view
2195 can_view_ISBD => C4::Context->preference('viewISBD'), # 1 if the staff search allows the ISBD view
2196 can_view_labeledMARC => C4::Context->preference('viewLabeledMARC'), # 1 if the staff search allows the Labeled MARC view
2197 );
2198}
2199
2200sub AddSearchHistory{
2201 my ($borrowernumber,$session,$query_desc,$query_cgi, $total)=@_;
2202 my $dbh = C4::Context->dbh;
2203
2204 # Add the request the user just made
2205 my $sql = "INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time) VALUES(?, ?, ?, ?, ?, NOW())";
2206 my $sth = $dbh->prepare($sql);
2207 $sth->execute($borrowernumber, $session, $query_desc, $query_cgi, $total);
2208 return $dbh->last_insert_id(undef, 'search_history', undef,undef,undef);
2209}
2210
2211sub GetSearchHistory{
2212 my ($borrowernumber,$session)=@_;
2213 my $dbh = C4::Context->dbh;
2214
2215 # Add the request the user just made
2216 my $query = "SELECT FROM search_history WHERE (userid=? OR sessionid=?)";
2217 my $sth = $dbh->prepare($query);
2218 $sth->execute($borrowernumber, $session);
2219 return $sth->fetchall_hashref({});
2220}
2221
2222=head2 z3950_search_args
2223
- -
2265sub z3950_search_args {
2266 my $bibrec = shift;
2267 my $isbn = Business::ISBN->new($bibrec);
2268
2269 if (defined $isbn && $isbn->is_valid)
2270 {
2271 $bibrec = { isbn => $bibrec } if !ref $bibrec;
2272 }
2273 else {
2274 $bibrec = { title => $bibrec } if !ref $bibrec;
2275 }
2276 my $array = [];
2277 for my $field (qw/ lccn isbn issn title author dewey subject /)
2278 {
2279 my $encvalue = URI::Escape::uri_escape_utf8($bibrec->{$field});
2280 push @$array, { name=>$field, value=>$bibrec->{$field}, encvalue=>$encvalue } if defined $bibrec->{$field};
2281 }
2282 return $array;
2283}
2284
2285=head2 GetDistinctValues($field);
2286
- -
2291sub GetDistinctValues {
2292 my ($fieldname,$string)=@_;
2293 # returns a reference to a hash of references to branches...
2294 if ($fieldname=~/\./){
2295 my ($table,$column)=split /\./, $fieldname;
2296 my $dbh = C4::Context->dbh;
2297 warn "select DISTINCT($column) as value, count(*) as cnt from $table group by lib order by $column " if $DEBUG;
2298 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 ");
2299 $sth->execute;
2300 my $elements=$sth->fetchall_arrayref({});
2301 return $elements;
2302 }
2303 else {
2304 $string||= qq("");
2305 my @servers=qw<biblioserver authorityserver>;
2306 my (@zconns,@results);
2307 for ( my $i = 0 ; $i < @servers ; $i++ ) {
2308 $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
2309 $results[$i] =
2310 $zconns[$i]->scan(
2311 ZOOM::Query::CCL2RPN->new( qq"$fieldname $string", $zconns[$i])
2312 );
2313 }
2314 # The big moment: asynchronously retrieve results from all servers
2315 my @elements;
2316 _ZOOM_event_loop(
2317 \@zconns,
2318 \@results,
2319 sub {
2320 my ( $i, $size ) = @_;
2321 for ( my $j = 0 ; $j < $size ; $j++ ) {
2322 my %hashscan;
2323 @hashscan{qw(value cnt)} =
2324 $results[ $i - 1 ]->display_term($j);
2325 push @elements, \%hashscan;
2326 }
2327 }
2328 );
2329 return \@elements;
2330 }
2331}
2332
2333=head2 _ZOOM_event_loop
2334
- -
2345
# spent 135ms (274µs+135) within C4::Search::_ZOOM_event_loop which was called: # once (274µs+135ms) by C4::Search::getRecords at line 671
sub _ZOOM_event_loop {
234629257µs my ($zconns, $results, $callback) = @_;
23471258.2ms while ( ( my $i = ZOOM::event( $zconns ) ) != 0 ) {
# spent 58.2ms making 12 calls to ZOOM::event, avg 4.85ms/call
234811206µs my $ev = $zconns->[ $i - 1 ]->last_event();
# spent 206µs making 11 calls to ZOOM::Connection::last_event, avg 19µs/call
23491171µs if ( $ev == ZOOM::Event::ZEND ) {
# spent 71µs making 11 calls to ZOOM::Event::ZEND, avg 6µs/call
2350 next unless $results->[ $i - 1 ];
2351146µs my $size = $results->[ $i - 1 ]->size();
# spent 46µs making 1 call to ZOOM::ResultSet::size
2352175.9ms if ( $size > 0 ) {
2353 $callback->($i, $size);
2354 }
2355 }
2356 }
2357
2358 foreach my $result (@$results) {
23591110µs $result->destroy();
# spent 110µs making 1 call to ZOOM::ResultSet::destroy
2360 }
2361}
2362
2363
2364162µs
# spent 5µs within C4::Search::END which was called: # once (5µs+0s) by main::RUNTIME at line 0 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
END { } # module clean-up code here (global destructor)
2365
2366117µs1;
2367__END__
 
# spent 4.71ms within C4::Search::CORE:match which was called 2109 times, avg 2µs/call: # 1496 times (1.16ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 525, avg 778ns/call # 275 times (1.68ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 518, avg 6µs/call # 252 times (1.75ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 521, avg 7µs/call # 23 times (33µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 592, avg 1µs/call # 23 times (25µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 609, avg 1µs/call # 23 times (8µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 623, avg 361ns/call # 5 times (9µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 650, avg 2µs/call # 2 times (1µs+0s) by C4::Search::parseQuery at line 1179, avg 650ns/call # once (13µs+0s) by C4::Search::getRecords at line 353 # once (7µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 500 # once (4µs+0s) by C4::Search::buildQuery at line 1389 # once (3µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 552 # once (2µs+0s) by C4::Search::buildQuery at line 1283 # once (600ns+0s) by C4::Search::buildQuery at line 1303 # once (500ns+0s) by C4::Search::buildQuery at line 1349 # once (400ns+0s) by C4::Search::buildQuery at line 1284 # once (400ns+0s) by C4::Search::buildQuery at line 1293 # once (400ns+0s) by C4::Search::buildQuery at line 1306
sub C4::Search::CORE:match; # opcode
# spent 6.54ms within C4::Search::CORE:regcomp which was called 1772 times, avg 4µs/call: # 1496 times (4.58ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 525, avg 3µs/call # 275 times (1.94ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 518, avg 7µs/call # once (17µs+0s) by C4::Search::parseQuery at line 1225
sub C4::Search::CORE:regcomp; # opcode
# spent 584µs within C4::Search::CORE:sort which was called 81 times, avg 7µs/call: # 25 times (221µs+0s) by C4::Search::searchResults at line 1996, avg 9µs/call # 25 times (35µs+0s) by C4::Search::searchResults at line 2004, avg 1µs/call # 25 times (27µs+0s) by C4::Search::searchResults at line 2000, avg 1µs/call # 5 times (285µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 561, avg 57µs/call # once (15µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 553
sub C4::Search::CORE:sort; # opcode
# spent 1.69ms (1.49+202µs) within C4::Search::CORE:subst which was called 558 times, avg 3µs/call: # 252 times (886µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 528, avg 4µs/call # 252 times (398µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 527, avg 2µs/call # 23 times (167µs+202µs) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 578, avg 16µs/call # 4 times (7µs+0s) by C4::Search::buildQuery at line 1587, avg 2µs/call # 4 times (2µs+0s) by C4::Search::buildQuery at line 1586, avg 550ns/call # 4 times (2µs+0s) by C4::Search::buildQuery at line 1589, avg 400ns/call # 4 times (2µs+0s) by C4::Search::buildQuery at line 1588, avg 375ns/call # 3 times (3µs+0s) by C4::Search::_detect_truncation at line 782, avg 867ns/call # 2 times (1µs+0s) by C4::Search::buildQuery at line 1594, avg 500ns/call # once (9µs+0s) by C4::Search::buildQuery at line 1343 # once (3µs+0s) by C4::Search::parseQuery at line 1178 # once (3µs+0s) by C4::Search::buildQuery at line 1354 # once (2µs+0s) by C4::Search::parseQuery at line 1225 # once (2µs+0s) by C4::Search::buildQuery at line 1581 # once (2µs+0s) by C4::Search::buildQuery at line 1591 # once (1µs+0s) by C4::Search::buildQuery at line 1582 # once (900ns+0s) by C4::Search::buildQuery at line 1584 # once (600ns+0s) by C4::Search::buildQuery at line 1583 # once (600ns+0s) by C4::Search::_detect_truncation at line 779
sub C4::Search::CORE:subst; # opcode