← 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 17:10:45 2013
Reported on Tue Oct 15 17:12:04 2013

Filename/usr/share/koha/lib/C4/Search.pm
StatementsExecuted 12040 statements in 91.9ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
11128.8ms113msC4::Search::::__ANON__[:670]C4::Search::__ANON__[:670]
11125.5ms5.46sC4::Search::::searchResultsC4::Search::searchResults
1772319.98ms9.98msC4::Search::::CORE:regcompC4::Search::CORE:regcomp (opcode)
21091816.40ms6.40msC4::Search::::CORE:matchC4::Search::CORE:match (opcode)
1115.02ms31.6msC4::Search::::parseQueryC4::Search::parseQuery
1114.70ms8.93msC4::Search::::BEGIN@24C4::Search::BEGIN@24
1113.39ms19.3msC4::Search::::BEGIN@29C4::Search::BEGIN@29
1112.89ms10.1msC4::Search::::BEGIN@36C4::Search::BEGIN@36
5581911.78ms1.96msC4::Search::::CORE:substC4::Search::CORE:subst (opcode)
1111.51ms1.77msC4::Search::::BEGIN@25C4::Search::BEGIN@25
111929µs5.78msC4::Search::::BEGIN@34C4::Search::BEGIN@34
8151501µs501µsC4::Search::::CORE:sortC4::Search::CORE:sort (opcode)
111287µs45.8msC4::Search::::buildQueryC4::Search::buildQuery
111275µs190msC4::Search::::getRecordsC4::Search::getRecords
111196µs181msC4::Search::::_ZOOM_event_loopC4::Search::_ZOOM_event_loop
11174µs74µsC4::Search::::getIndexesC4::Search::getIndexes
11141µs56µsC4::Search::::_build_weighted_queryC4::Search::_build_weighted_query
11136µs129µsC4::Search::::BEGIN@26C4::Search::BEGIN@26
11133µs42µsC4::Search::::BEGIN@18C4::Search::BEGIN@18
11129µs824µsC4::Search::::BEGIN@23C4::Search::BEGIN@23
11129µs1.36msC4::Search::::BEGIN@22C4::Search::BEGIN@22
11128µs30µsC4::Search::::_detect_truncationC4::Search::_detect_truncation
11128µs100µsC4::Search::::BEGIN@28C4::Search::BEGIN@28
11126µs93µsC4::Search::::BEGIN@27C4::Search::BEGIN@27
11125µs314µsC4::Search::::BEGIN@30C4::Search::BEGIN@30
11124µs133µsC4::Search::::BEGIN@2010C4::Search::BEGIN@2010
11122µs27µsC4::Search::::BEGIN@21C4::Search::BEGIN@21
11122µs44µsC4::Search::::BEGIN@37C4::Search::BEGIN@37
11117µs22µsC4::Search::::BEGIN@39C4::Search::BEGIN@39
11116µs88µsC4::Search::::BEGIN@33C4::Search::BEGIN@33
11115µs105µsC4::Search::::BEGIN@32C4::Search::BEGIN@32
11115µs19µsC4::Search::::BEGIN@38C4::Search::BEGIN@38
11115µs69µsC4::Search::::BEGIN@35C4::Search::BEGIN@35
11114µs370µsC4::Search::::BEGIN@31C4::Search::BEGIN@31
11111µs99µsC4::Search::::BEGIN@40C4::Search::BEGIN@40
1117µs7µsC4::Search::::BEGIN@43C4::Search::BEGIN@43
1116µs6µ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
18362µs252µs
# spent 42µs (33+9) within C4::Search::BEGIN@18 which was called: # once (33µs+9µs) by main::BEGIN@48 at line 18
use strict;
# spent 42µs making 1 call to C4::Search::BEGIN@18 # spent 9µs making 1 call to strict::import
19#use warnings; FIXME - Bug 2505
2012µsrequire Exporter;
21355µs232µs
# spent 27µs (22+5) within C4::Search::BEGIN@21 which was called: # once (22µs+5µs) by main::BEGIN@48 at line 21
use C4::Context;
# spent 27µs making 1 call to C4::Search::BEGIN@21 # spent 5µs making 1 call to C4::Context::import
22368µs22.69ms
# spent 1.36ms (29µs+1.33) within C4::Search::BEGIN@22 which was called: # once (29µs+1.33ms) by main::BEGIN@48 at line 22
use C4::Biblio; # GetMarcFromKohaField, GetBiblioData
# spent 1.36ms making 1 call to C4::Search::BEGIN@22 # spent 1.33ms making 1 call to Exporter::import
23370µs21.62ms
# spent 824µs (29+795) within C4::Search::BEGIN@23 which was called: # once (29µs+795µs) by main::BEGIN@48 at line 23
use C4::Koha; # getFacets
# spent 824µs making 1 call to C4::Search::BEGIN@23 # spent 795µs making 1 call to Exporter::import
243245µs28.99ms
# spent 8.93ms (4.70+4.23) within C4::Search::BEGIN@24 which was called: # once (4.70ms+4.23ms) by main::BEGIN@48 at line 24
use Lingua::Stem;
# spent 8.93ms making 1 call to C4::Search::BEGIN@24 # spent 58µs making 1 call to Exporter::import
253255µs21.78ms
# spent 1.77ms (1.51+264µs) within C4::Search::BEGIN@25 which was called: # once (1.51ms+264µs) by main::BEGIN@48 at line 25
use C4::Search::PazPar2;
# spent 1.77ms making 1 call to C4::Search::BEGIN@25 # spent 4µs making 1 call to UNIVERSAL::import
26363µs2149µs
# spent 129µs (36+93) within C4::Search::BEGIN@26 which was called: # once (36µs+93µs) by main::BEGIN@48 at line 26
use XML::Simple;
# spent 129µs making 1 call to C4::Search::BEGIN@26 # spent 20µs making 1 call to XML::Simple::import
27373µs2161µs
# spent 93µs (26+68) within C4::Search::BEGIN@27 which was called: # once (26µs+68µs) by main::BEGIN@48 at line 27
use C4::Dates qw(format_date);
# spent 93µs making 1 call to C4::Search::BEGIN@27 # spent 68µs making 1 call to Exporter::import
28367µs2172µs
# spent 100µs (28+72) within C4::Search::BEGIN@28 which was called: # once (28µs+72µs) by main::BEGIN@48 at line 28
use C4::Members qw(GetHideLostItemsPreference);
# spent 100µs making 1 call to C4::Search::BEGIN@28 # spent 72µs making 1 call to Exporter::import
293213µs219.5ms
# spent 19.3ms (3.39+15.9) within C4::Search::BEGIN@29 which was called: # once (3.39ms+15.9ms) by main::BEGIN@48 at line 29
use C4::XSLT;
# spent 19.3ms making 1 call to C4::Search::BEGIN@29 # spent 207µs making 1 call to Exporter::import
30343µs2602µs
# spent 314µs (25+288) within C4::Search::BEGIN@30 which was called: # once (25µs+288µs) by main::BEGIN@48 at line 30
use C4::Branch;
# spent 314µs making 1 call to C4::Search::BEGIN@30 # spent 288µs making 1 call to Exporter::import
31338µs2726µs
# spent 370µs (14+356) within C4::Search::BEGIN@31 which was called: # once (14µs+356µs) by main::BEGIN@48 at line 31
use C4::Reserves; # GetReserveStatus
# spent 370µs making 1 call to C4::Search::BEGIN@31 # spent 356µs making 1 call to Exporter::import
32336µs2195µs
# spent 105µs (15+90) within C4::Search::BEGIN@32 which was called: # once (15µs+90µs) by main::BEGIN@48 at line 32
use C4::Debug;
# spent 105µs making 1 call to C4::Search::BEGIN@32 # spent 90µs making 1 call to Exporter::import
33335µs2160µs
# spent 88µs (16+72) within C4::Search::BEGIN@33 which was called: # once (16µs+72µs) by main::BEGIN@48 at line 33
use C4::Charset;
# spent 88µs making 1 call to C4::Search::BEGIN@33 # spent 72µs making 1 call to Exporter::import
343162µs25.82ms
# spent 5.78ms (929µs+4.85) within C4::Search::BEGIN@34 which was called: # once (929µs+4.85ms) by main::BEGIN@48 at line 34
use YAML;
# spent 5.78ms making 1 call to C4::Search::BEGIN@34 # spent 42µs making 1 call to Exporter::import
35341µs2123µs
# spent 69µs (15+54) within C4::Search::BEGIN@35 which was called: # once (15µs+54µs) by main::BEGIN@48 at line 35
use URI::Escape;
# spent 69µs making 1 call to C4::Search::BEGIN@35 # spent 54µs making 1 call to Exporter::import
363142µs210.1ms
# spent 10.1ms (2.89+7.21) within C4::Search::BEGIN@36 which was called: # once (2.89ms+7.21ms) by main::BEGIN@48 at line 36
use Business::ISBN;
# spent 10.1ms making 1 call to C4::Search::BEGIN@36 # spent 26µs making 1 call to Exporter::import
37339µs265µs
# spent 44µs (22+21) within C4::Search::BEGIN@37 which was called: # once (22µs+21µs) by main::BEGIN@48 at line 37
use MARC::Record;
# spent 44µs making 1 call to C4::Search::BEGIN@37 # spent 21µs making 1 call to Exporter::import
38336µs223µs
# spent 19µs (15+4) within C4::Search::BEGIN@38 which was called: # once (15µs+4µs) by main::BEGIN@48 at line 38
use MARC::Field;
# spent 19µs making 1 call to C4::Search::BEGIN@38 # spent 4µs making 1 call to UNIVERSAL::import
39340µs227µs
# spent 22µs (17+5) within C4::Search::BEGIN@39 which was called: # once (17µs+5µs) by main::BEGIN@48 at line 39
use utf8;
# spent 22µs making 1 call to C4::Search::BEGIN@39 # spent 5µs making 1 call to utf8::import
40353µs2188µs
# spent 99µs (11+88) within C4::Search::BEGIN@40 which was called: # once (11µs+88µs) by main::BEGIN@48 at line 40
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG);
# spent 99µs making 1 call to C4::Search::BEGIN@40 # spent 88µ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.05ms17µs}
# spent 7µs making 1 call to C4::Search::BEGIN@43
47
48=head1 NAME
49
- -
64114µs@ISA = qw(Exporter);
6514µ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 190ms (275µs+189) within C4::Search::getRecords which was called: # once (275µs+189ms) by main::RUNTIME at line 523 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub getRecords {
324 my (
32516190µ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 = ();
34212.16ms my $facets = getFacets();
# spent 2.16ms making 1 call to C4::Koha::getFacets
34312.31ms my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets')||20;
# spent 2.31ms 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
348844µs for ( my $i = 0 ; $i < @servers ; $i++ ) {
3491793µs $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
# spent 793µ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
353114µs my $query_to_use = ($servers[$i] =~ /biblioserver/) ? $koha_query : $simple_query;
# spent 14µ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
35912µs eval {
360118µs 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 {
37422.74ms $results[$i] = $zconns[$i]->search(new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i]));
# spent 2.59ms making 1 call to ZOOM::Query::CCL2RPN::new # spent 151µ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) {
38528µs 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 113ms (28.8+84.2) within C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] which was called: # once (28.8ms+84.2ms) by C4::Search::_ZOOM_event_loop at line 2352
sub {
43710135µs 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 }
4497551µs for ( my $j = $offset ; $j < $times ; $j++ ) {
450 my $records_hash;
451 my $record;
452
453 ## Check if it's an index scan
45450482µs 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 {
4905036.5ms $record = $results[ $i - 1 ]->record($j)->raw();
# spent 32.8ms making 25 calls to ZOOM::ResultSet::record, avg 1.31ms/call # spent 3.69ms making 25 calls to ZOOM::Record::raw, avg 147µ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
50026µs16µs if ( !$scan && $servers[ $i - 1 ] =~ /biblioserver/ ) {
# spent 6µ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) {
5055253.57ms for ( my $j = 0 ; $j < $jmax ; $j++ ) {
50635027.4ms my $render_record =
# spent 14.8ms making 175 calls to ZOOM::Record::render, avg 84µs/call # spent 12.6ms making 175 calls to ZOOM::ResultSet::record, avg 72µs/call
507 $results[ $i - 1 ]->record($j)->render();
508 my @used_datas = ();
509 foreach my $tag ( @{ $facet->{tags} } ) {
510
511 # avoid first line
512165010.6ms 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 );
5185505.50ms my @field_tokens =
# spent 3.33ms making 275 calls to C4::Search::CORE:regcomp, avg 12µs/call # spent 2.17ms making 275 calls to C4::Search::CORE:match, avg 8µs/call
519 ( $render_record =~ /$field_pattern/g );
520 foreach my $field_token (@field_tokens) {
52115129.56ms2522.48ms my @subf = ( $field_token =~
# spent 2.48ms making 252 calls to C4::Search::CORE:match, avg 10µs/call
522 /\$([a-zA-Z0-9]) ([^\$]+)/g );
523 my @values;
524149616.1ms for ( my $i = 0 ; $i < @subf ; $i += 2 ) {
52510084.13ms29928.30ms if ( $letters =~ $subf[$i] ) {
# spent 6.63ms making 1496 calls to C4::Search::CORE:regcomp, avg 4µs/call # spent 1.67ms making 1496 calls to C4::Search::CORE:match, avg 1µs/call
526 my $value = $subf[ $i + 1 ];
527252566µs $value =~ s/^ *//;
# spent 566µs making 252 calls to C4::Search::CORE:subst, avg 2µs/call
5282521.07ms $value =~ s/ *$//;
# spent 1.07ms 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 );
533432759µs unless ( $data ~~ @used_datas ) {
534 $facets_counter->{ $facet->{idx} }
535 ->{$data}++;
536 push @used_datas, $data;
537 }
538 } # fields
539 } # field codes
54028390µs } # 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
552129µs13µ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 {
55830404µs my $expandable;
559 my $number_of_facets;
560 my @this_facets_array;
5615247µs for my $one_facet (
# spent 247µs making 5 calls to C4::Search::CORE:sort, avg 49µ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 {
569306198µs $number_of_facets++;
570207606µs 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µs24481µs $facet_link_value =~ s/[()!?¡¿؟]/ /g;
# spent 303µs making 23 calls to C4::Search::CORE:subst, avg 13µs/call # spent 178µ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;
582231.81ms my $facet_max_length = C4::Context->preference(
# spent 1.81ms making 23 calls to C4::Context::preference, avg 79µ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,
5922323µs if ( $link_value =~ /branch/ ) {
# spent 23µ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,
6092315µs if ( $link_value =~ /itype/ ) {
# spent 15µs making 23 calls to C4::Search::CORE:match, avg 635ns/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
623236µs if ( $link_value =~ /location/ ) {
# spent 6µs making 23 calls to C4::Search::CORE:match, avg 265ns/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 }
65068µs push @facets_loop,
# spent 4µs making 1 call to C4::Context::preference # spent 4µs making 5 calls to C4::Search::CORE:match, avg 720ns/call
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 }
6711181ms );
# spent 181ms 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 30µs (28+2) within C4::Search::_detect_truncation which was called: # once (28µs+2µs) by C4::Search::buildQuery at line 1418
sub _detect_truncation {
776619µs my ( $operand, $index ) = @_;
777 my ( @nontruncated, @righttruncated, @lefttruncated, @rightlefttruncated,
778 @regexpr );
77911µs $operand =~ s/^ //g;
# spent 1µs making 1 call to C4::Search::CORE:subst
780 my @wordlist = split( /\s/, $operand );
781 foreach my $word (@wordlist) {
782112µs32µs if ( $word =~ s/^\*([^\*]+)\*$/$1/ ) {
# spent 2µs making 3 calls to C4::Search::CORE:subst, avg 500ns/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 56µs (41+15) within C4::Search::_build_weighted_query which was called: # once (41µs+15µ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
846825µs my ( $operand, $stemmed_operand, $index ) = @_;
84719µs my $stemming = C4::Context->preference("QueryStemming") || 0;
# spent 9µ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
84913µs my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
# spent 3µ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
85466µs 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 74µs within C4::Search::getIndexes which was called: # once (74µs+0s) by C4::Search::buildQuery at line 1282
sub getIndexes{
912277µ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 31.6ms (5.02+26.6) within C4::Search::parseQuery which was called: # once (5.02ms+26.6ms) by C4::Search::buildQuery at line 1253
sub parseQuery {
11641655µ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;
117822.18ms $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') || $query =~ s/^qp=//);
# spent 2.18ms making 1 call to C4::Context::preference # spent 2µs making 1 call to C4::Search::CORE:subst
117922µs undef $QParser if ($query =~ m/^(ccl=|pqf=|cql=)/ || grep (/\w,\w|\w=\w/, @operands, @indexes) );
# spent 2µs making 2 calls to C4::Search::CORE:match, avg 900ns/call
1180 undef $QParser if (scalar @limits > 0);
1181
11824275µs 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;
1224118µs my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')';
# spent 18µs making 1 call to OpenILS::QueryParser::modifiers
1225221µs s/$modifier_re//g for @operands;
# spent 20µ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 45.8ms (287µs+45.5) within C4::Search::buildQuery which was called: # once (287µs+45.5ms) by main::RUNTIME at line 453 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub buildQuery {
124844129µs my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
1249
1250 warn "---------\nEnter buildQuery\n---------" if $DEBUG;
1251
1252 my $query_desc;
1253131.6ms ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang);
# spent 31.6ms 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
126214.25ms my $stemming = C4::Context->preference("QueryStemming") || 0;
# spent 4.25ms making 1 call to C4::Context::preference
126312.16ms my $auto_truncation = C4::Context->preference("QueryAutoTruncate") || 0;
# spent 2.16ms making 1 call to C4::Context::preference
126412.53ms my $weight_fields = C4::Context->preference("QueryWeightFields") || 0;
# spent 2.53ms making 1 call to C4::Context::preference
126512.11ms my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
# spent 2.11ms making 1 call to C4::Context::preference
126612.63ms my $remove_stopwords = C4::Context->preference("QueryRemoveStopwords") || 0;
# spent 2.63ms 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;
1282174µs my $cclindexes = getIndexes();
# spent 74µs making 1 call to C4::Search::getIndexes
128326µs12µ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 }
130646µs1500ns if ( $query =~ /^pqf=/ ) {
# spent 500ns 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
133922µs for ( my $i = 0 ; $i <= @operands ; $i++ ) {
1340
1341 # COMBINE OPERANDS, INDEXES AND OPERATORS
13422663µs if ( $operands[$i] ) {
134314µs $operands[$i]=~s/^\s+//;
# spent 4µ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
134914µs1500ns 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 {
135411µs $operands[$i] =~ s/\?/{?}/g; # need to escape question marks
# spent 1µ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{};
138912µs unless ( $indexes_set || !$index || $index =~ /(st-|phr|ext|wrdl|nb|ns)/ ) {
# spent 2µ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;
1418130µs my( $nontruncated, $righttruncated, $lefttruncated,
# spent 30µ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;
146739µs if ($weight_fields) {
1468156µs $weighted_operand = _build_weighted_query( $operand, $stemmed_operand, $index );
# spent 56µ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
1476612µs 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]";
1508137µs $query_cgi .= "&idx=".uri_escape($index) if $index;
# spent 37µ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
15821700ns $query =~ s/(?<=(wrdl)):/=/g;
# spent 700ns making 1 call to C4::Search::CORE:subst
15831800ns $query =~ s/(?<=(trn|phr)):/=/g;
# spent 800ns making 1 call to C4::Search::CORE:subst
15841800ns $limit =~ s/:/=/g;
# spent 800ns making 1 call to C4::Search::CORE:subst
1585 for ( $query, $query_desc, $limit, $limit_desc ) {
15861648µs42µs s/ +/ /g; # remove extra spaces
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 525ns/call
158745µs s/^ //g; # remove any beginning spaces
# spent 5µs making 4 calls to C4::Search::CORE:subst, avg 1µs/call
158841µs s/ $//g; # remove any ending spaces
# spent 1µ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 475ns/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) {
159426µs2900ns s/"//g;
# spent 900ns making 2 calls to C4::Search::CORE:subst, avg 450ns/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 5.46s (25.5ms+5.44) within C4::Search::searchResults which was called: # once (25.5ms+5.44s) by main::RUNTIME at line 560 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub searchResults {
1629297.73ms my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults ) = @_;
163011.09ms my $dbh = C4::Context->dbh;
# spent 1.09ms 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);
1637211µs if ($search_context eq 'opac') {
1638112µs $hidelostitems = C4::Context->preference('hidelostitems');
# spent 12µ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;
1646110µs2249µs my $bsth =$dbh->prepare("SELECT branchcode,branchname FROM branches"); # FIXME : use C4::Branch::GetBranches
# spent 134µs making 1 call to DBI::db::prepare # spent 114µs making 1 call to DBD::mysql::db::prepare
16471925µs $bsth->execute();
# spent 925µs making 1 call to DBI::st::execute
164833667µs while ( my $bdata = $bsth->fetchrow_hashref ) {
# spent 517µs making 11 calls to DBI::st::fetchrow_hashref, avg 47µs/call # spent 75µs making 11 calls to DBI::common::FETCH, avg 7µs/call # spent 75µs making 11 calls to DBI::st::fetch, avg 7µ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.16ms my $shelflocations =GetKohaAuthorisedValues('items.location','');
# spent 4.14ms making 1 call to C4::Koha::GetKohaAuthorisedValues # spent 10µs making 2 calls to DBI::common::DESTROY, avg 5µs/call # spent 5µs making 1 call to DBD::_mem::common::DESTROY
1655
1656 # get notforloan authorised value list (see $shelflocations FIXME)
165741.71ms my $notforloan_authorised_value = GetAuthValCode('items.notforloan','');
# spent 1.67ms making 1 call to C4::Koha::GetAuthValCode # spent 29µs making 2 calls to DBI::common::DESTROY, avg 14µs/call # spent 5µs making 1 call to DBD::_mem::common::DESTROY
1658
1659 #Build itemtype hash
1660 #find itemtype & itemtype image
1661 my %itemtypes;
1662138µs2176µs $bsth =
# spent 95µs making 1 call to DBI::db::prepare # spent 80µs making 1 call to DBD::mysql::db::prepare
1663 $dbh->prepare(
1664 "SELECT itemtype,description,imageurl,summary,notforloan FROM itemtypes"
1665 );
16664789µs $bsth->execute();
# spent 778µs making 1 call to DBI::st::execute # spent 9µs making 2 calls to DBI::common::DESTROY, avg 5µs/call # spent 3µs making 1 call to DBD::_mem::common::DESTROY
166772974µs while ( my $bdata = $bsth->fetchrow_hashref ) {
# spent 708µs making 24 calls to DBI::st::fetchrow_hashref, avg 29µs/call # spent 139µs making 24 calls to DBI::st::fetch, avg 6µs/call # spent 127µs making 24 calls to DBI::common::FETCH, avg 5µs/call
1668 foreach (qw(description imageurl summary notforloan)) {
166992231µs $itemtypes{ $bdata->{'itemtype'} }->{$_} = $bdata->{$_};
1670 }
1671 }
1672
1673 #search item field code
1674112.8ms my ($itemtag, undef) = &GetMarcFromKohaField( "items.itemnumber", "" );
# spent 12.8ms making 1 call to C4::Biblio::GetMarcFromKohaField
1675
1676 ## find column names of items related to MARC
167715µs2110µs my $sth2 = $dbh->prepare("SHOW COLUMNS FROM items");
# spent 60µs making 1 call to DBI::db::prepare # spent 51µs making 1 call to DBD::mysql::db::prepare
167813.75ms $sth2->execute;
# spent 3.75ms making 1 call to DBI::st::execute
1679 my %subfieldstosearch;
168080165µs41175µs while ( ( my $column ) = $sth2->fetchrow ) {
# spent 175µs making 41 calls to DBI::st::fetchrow, avg 4µs/call
168140371µs my ( $tagfield, $tagsubfield ) =
# spent 371µs making 40 calls to C4::Biblio::GetMarcFromKohaField, avg 9µ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
169518µs my $marcflavour = C4::Context->preference("marcflavour");
# spent 8µs making 1 call to C4::Context::preference
1696 # We get the biblionumber position in MARC
169718µs my ($bibliotag,$bibliosubf)=GetMarcFromKohaField('biblio.biblionumber','');
# spent 8µs making 1 call to C4::Biblio::GetMarcFromKohaField
1698
1699 # loop through all of the records we've retrieved
170019009.68ms for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) {
170125121ms my $marcrecord = MARC::File::USMARC::decode( $marcresults->[$i] );
# spent 121ms making 25 calls to MARC::File::USMARC::decode, avg 4.85ms/call
170212574.5ms my $fw = $scan
# spent 66.7ms making 25 calls to C4::Biblio::GetFrameworkCode, avg 2.67ms/call # spent 7.32ms making 25 calls to MARC::Record::subfield, avg 293µs/call # spent 330µs making 50 calls to DBI::common::DESTROY, avg 7µs/call # spent 84µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call
1703 ? undef
1704 : $bibliotag < 10
1705 ? GetFrameworkCode($marcrecord->field($bibliotag)->data)
1706 : GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf));
17072531.0ms my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, $fw );
# spent 31.0ms making 25 calls to C4::Biblio::TransformMarcToKoha, avg 1.24ms/call
170810058.2ms $oldbiblio->{subtitle} = GetRecordValue('subtitle', $marcrecord, $fw);
# spent 57.9ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.32ms/call # spent 248µs making 50 calls to DBI::common::DESTROY, avg 5µs/call # spent 57µs making 25 calls to DBD::_mem::common::DESTROY, avg 2µs/call
170910062.8ms $oldbiblio->{source_t} = GetRecordValue('source_t', $marcrecord, $fw);
# spent 62.4ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.50ms/call # spent 274µs making 50 calls to DBI::common::DESTROY, avg 5µs/call # spent 67µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call
171010054.9ms $oldbiblio->{source_g} = GetRecordValue('source_g', $marcrecord, $fw);
# spent 54.6ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.19ms/call # spent 215µs making 50 calls to DBI::common::DESTROY, avg 4µs/call # spent 54µs making 25 calls to DBD::_mem::common::DESTROY, avg 2µs/call
1711 $oldbiblio->{result_number} = $i + 1;
1712
1713 # add imageurl to itemtype if there is one
1714252.05ms $oldbiblio->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} );
# spent 2.05ms making 25 calls to C4::Koha::getitemtypeimagelocation, avg 82µs/call
1715
171625139µ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 139µs making 25 calls to C4::Context::preference, avg 6µs/call
1717259.00ms $oldbiblio->{normalized_upc} = GetNormalizedUPC( $marcrecord,$marcflavour);
# spent 9.00ms making 25 calls to C4::Koha::GetNormalizedUPC, avg 360µs/call
1718257.68ms $oldbiblio->{normalized_ean} = GetNormalizedEAN( $marcrecord,$marcflavour);
# spent 7.68ms making 25 calls to C4::Koha::GetNormalizedEAN, avg 307µs/call
1719257.58ms $oldbiblio->{normalized_oclc} = GetNormalizedOCLCNumber($marcrecord,$marcflavour);
# spent 7.58ms making 25 calls to C4::Koha::GetNormalizedOCLCNumber, avg 303µs/call
1720256.88ms $oldbiblio->{normalized_isbn} = GetNormalizedISBN(undef,$marcrecord,$marcflavour);
# spent 6.88ms making 25 calls to C4::Koha::GetNormalizedISBN, avg 275µ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
1777257.00ms my @fields = $marcrecord->field($itemtag);
# spent 7.00ms making 25 calls to MARC::Record::field, avg 280µs/call
177825167µs my $marcflavor = C4::Context->preference("marcflavour");
# spent 167µs making 25 calls to C4::Context::preference, avg 7µ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 }
1786257.15ms foreach my $hostfield ( $marcrecord->field($analyticsfield)) {
# spent 7.15ms making 25 calls to MARC::Record::field, avg 286µs/call
178727129µs9199µs my $hostbiblionumber = $hostfield->subfield("0");
# spent 199µs making 9 calls to MARC::Field::subfield, avg 22µs/call
17889107µs my $linkeditemnumber = $hostfield->subfield("9");
# spent 107µs making 9 calls to MARC::Field::subfield, avg 12µ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.68ms my $maxitems_pref = C4::Context->preference('maxItemsinSearchResults');
# spent 2.68ms making 25 calls to C4::Context::preference, avg 107µ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) {
18353202.65ms my $item;
1836
1837 # populate the items hash
1838 foreach my $code ( keys %subfieldstosearch ) {
183912806.64ms128057.8ms $item->{$code} = $field->subfield( $subfieldstosearch{$code} );
# spent 57.8ms making 1280 calls to MARC::Field::subfield, avg 45µs/call
1840 }
1841 $item->{description} = $itemtypes{ $item->{itype} }{description};
1842
1843 # OPAC hidden items
184496304µs if ($is_opac) {
1845 # hidden because lost
1846 if ($hidelostitems && $item->{itemlost}) {
1847 $hideatopac_count++;
1848 next;
1849 }
1850 # hidden based on OpacHiddenItems syspref
185132125ms my @hi = C4::Items::GetHiddenItemnumbers($item);
# spent 125ms making 32 calls to C4::Items::GetHiddenItemnumbers, avg 3.90ms/call
1852 if (scalar @hi) {
1853 push @hiddenitems, @hi;
1854 $hideatopac_count++;
1855 next;
1856 }
1857 }
1858
1859321.85ms my $hbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'homebranch' : 'holdingbranch';
# spent 1.85ms making 32 calls to C4::Context::preference, avg 58µs/call
186032119µs my $otherbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'holdingbranch' : 'homebranch';
# spent 119µs making 32 calls to C4::Context::preference, avg 4µ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
187232302µs my $userenv = C4::Context->userenv;
# spent 302µs making 32 calls to C4::Context::userenv, avg 9µs/call
1873192712µs 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
190764649µs 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 #
192512876.5ms ($transfertwhen, $transfertfrom, $transfertto) = C4::Circulation::GetTransfers($item->{itemnumber});
# spent 76.1ms making 32 calls to C4::Circulation::GetTransfers, avg 2.38ms/call # spent 320µs making 64 calls to DBI::common::DESTROY, avg 5µs/call # spent 90µs making 32 calls to DBD::_mem::common::DESTROY, avg 3µs/call
1926128118ms $reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber}, $oldbiblio->{biblionumber} );
# spent 117ms making 32 calls to C4::Reserves::GetReserveStatus, avg 3.67ms/call # spent 377µs making 64 calls to DBI::common::DESTROY, avg 6µs/call # spent 70µ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
19301921.03ms 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)) {
198296326µs $available_items->{$prefix}->{$_} = $item->{$_};
1983 }
1984 $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} };
1985322.79ms $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
# spent 2.79ms making 32 calls to C4::Koha::getitemtypeimagelocation, avg 87µ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 );
199625168µs for my $key ( sort keys %$onloan_items ) {
# spent 168µs making 25 calls to C4::Search::CORE:sort, avg 7µs/call
1997 (++$onloanitemscount > $maxitems) and last;
1998 push @onloan_items_loop, $onloan_items->{$key};
1999 }
20002534µs for my $key ( sort keys %$other_items ) {
# spent 34µ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 }
20042537µs for my $key ( sort keys %$available_items ) {
# spent 37µs making 25 calls to C4::Search::CORE:sort, avg 1µs/call
200559118µs (++$availableitemscount > $maxitems) and last;
2006 push @available_items_loop, $available_items->{$key}
2007 }
2008
2009 # XSLT processing of some stuff
201032.24ms2242µs
# spent 133µs (24+109) within C4::Search::BEGIN@2010 which was called: # once (24µs+109µs) by main::BEGIN@48 at line 2010
use C4::Charset;
# spent 133µs making 1 call to C4::Search::BEGIN@2010 # spent 109µs making 1 call to Exporter::import
201125114ms SetUTF8Flag($marcrecord);
# spent 114ms making 25 calls to C4::Charset::SetUTF8Flag, avg 4.56ms/call
2012 warn $marcrecord->as_formatted if $DEBUG;
2013 my $interface = $search_context eq 'opac' ? 'OPAC' : '';
20141004.46s if (!$scan && C4::Context->preference($interface . "XSLTResultsDisplay")) {
# spent 4.46s making 25 calls to C4::XSLT::XSLTParse4Display, avg 178ms/call # spent 1.40ms making 50 calls to XML::LibXML::Node::DESTROY, avg 28µs/call # spent 356µ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
202025226µs if (!C4::Context->preference("item-level_itypes")) {
# spent 226µ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
204425111µs if (C4::Context->preference("AlternateHoldingsField") && $items_count == 0) {
# spent 111µs making 25 calls to C4::Context::preference, avg 4µ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 181ms (196µs+181) within C4::Search::_ZOOM_event_loop which was called: # once (196µs+181ms) by C4::Search::getRecords at line 671
sub _ZOOM_event_loop {
2346360µs my ($zconns, $results, $callback) = @_;
23472282µs1267.7ms while ( ( my $i = ZOOM::event( $zconns ) ) != 0 ) {
# spent 67.7ms making 12 calls to ZOOM::event, avg 5.64ms/call
234811202µs my $ev = $zconns->[ $i - 1 ]->last_event();
# spent 202µs making 11 calls to ZOOM::Connection::last_event, avg 18µs/call
2349317µs1161µs if ( $ev == ZOOM::Event::ZEND ) {
# spent 61µs making 11 calls to ZOOM::Event::ZEND, avg 6µs/call
2350 next unless $results->[ $i - 1 ];
2351171µs my $size = $results->[ $i - 1 ]->size();
# spent 71µs making 1 call to ZOOM::ResultSet::size
23521113ms if ( $size > 0 ) {
2353 $callback->($i, $size);
2354 }
2355 }
2356 }
2357
2358 foreach my $result (@$results) {
235916µs177µs $result->destroy();
# spent 77µs making 1 call to ZOOM::ResultSet::destroy
2360 }
2361}
2362
2363
23641115µs
# spent 6µs within C4::Search::END which was called: # once (6µ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
2366111µs1;
2367__END__
 
# spent 6.40ms within C4::Search::CORE:match which was called 2109 times, avg 3µs/call: # 1496 times (1.67ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 525, avg 1µs/call # 275 times (2.17ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 518, avg 8µs/call # 252 times (2.48ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 521, avg 10µs/call # 23 times (23µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 592, avg 1µs/call # 23 times (15µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 609, avg 635ns/call # 23 times (6µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 623, avg 265ns/call # 5 times (4µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 650, avg 720ns/call # 2 times (2µs+0s) by C4::Search::parseQuery at line 1179, avg 900ns/call # once (14µs+0s) by C4::Search::getRecords at line 353 # once (6µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 500 # 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 (2µs+0s) by C4::Search::buildQuery at line 1389 # once (600ns+0s) by C4::Search::buildQuery at line 1303 # once (500ns+0s) by C4::Search::buildQuery at line 1306 # 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
sub C4::Search::CORE:match; # opcode
# spent 9.98ms within C4::Search::CORE:regcomp which was called 1772 times, avg 6µs/call: # 1496 times (6.63ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 525, avg 4µs/call # 275 times (3.33ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 518, avg 12µs/call # once (20µs+0s) by C4::Search::parseQuery at line 1225
sub C4::Search::CORE:regcomp; # opcode
# spent 501µs within C4::Search::CORE:sort which was called 81 times, avg 6µs/call: # 25 times (168µs+0s) by C4::Search::searchResults at line 1996, avg 7µs/call # 25 times (37µs+0s) by C4::Search::searchResults at line 2004, avg 1µs/call # 25 times (34µs+0s) by C4::Search::searchResults at line 2000, avg 1µs/call # 5 times (247µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 561, avg 49µ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.96ms (1.78+178µs) within C4::Search::CORE:subst which was called 558 times, avg 4µs/call: # 252 times (1.07ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 528, avg 4µs/call # 252 times (566µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 527, avg 2µs/call # 23 times (125µs+178µs) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 578, avg 13µs/call # 4 times (5µs+0s) by C4::Search::buildQuery at line 1587, avg 1µs/call # 4 times (2µs+0s) by C4::Search::buildQuery at line 1586, avg 525ns/call # 4 times (2µs+0s) by C4::Search::buildQuery at line 1589, avg 475ns/call # 4 times (1µs+0s) by C4::Search::buildQuery at line 1588, avg 375ns/call # 3 times (2µs+0s) by C4::Search::_detect_truncation at line 782, avg 500ns/call # 2 times (900ns+0s) by C4::Search::buildQuery at line 1594, avg 450ns/call # once (4µs+0s) by C4::Search::buildQuery at line 1343 # once (2µs+0s) by C4::Search::parseQuery at line 1178 # once (2µs+0s) by C4::Search::buildQuery at line 1591 # once (2µs+0s) by C4::Search::buildQuery at line 1581 # once (2µs+0s) by C4::Search::parseQuery at line 1225 # once (1µs+0s) by C4::Search::buildQuery at line 1354 # once (1µs+0s) by C4::Search::_detect_truncation at line 779 # once (800ns+0s) by C4::Search::buildQuery at line 1584 # once (800ns+0s) by C4::Search::buildQuery at line 1583 # once (700ns+0s) by C4::Search::buildQuery at line 1582
sub C4::Search::CORE:subst; # opcode