← 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:48 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 {
4411µs $VERSION = 3.07.00.049;
4517µs $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 (
32515µ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
33013µs my @servers = @$servers_ref;
33112µs my @sort_by = @$sort_by_ref;
332
333 # Initialize variables for the ZOOM connection and results object
3341500ns my $zconn;
3351400ns my @zconns;
3361300ns my @results;
3371700ns my $results_hashref = ();
338
339 # Initialize variables for the faceted results objects
3401400ns my $facets_counter = ();
3411500ns my $facets_info = ();
34218µs12.16ms my $facets = getFacets();
# spent 2.16ms making 1 call to C4::Koha::getFacets
34318µs12.31ms my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets')||20;
# spent 2.31ms making 1 call to C4::Context::preference
344
3451600ns my @facets_loop; # stores the ref to array of hashes for template facets loop
346
347 ### LOOP THROUGH THE SERVERS
34815µs for ( my $i = 0 ; $i < @servers ; $i++ ) {
34918µs1793µ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
353126µs114µ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;
35611µs 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 {
36012µ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 {
374118µs22.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 };
3771400ns 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
3831700ns my $sort_by;
38413µs foreach my $sort (@sort_by) {
38517µ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 {
4221400ns warn "Ignoring unrecognized sort '$sort' requested" if $sort_by;
423 }
424 }
4251700ns if ($sort_by && !$scan) {
426 if ( $results[$i]->sort( "yaz", $sort_by ) < 0 ) {
427 warn "WARNING sort $sort_by failed";
428 }
429 }
4301800ns } # 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 {
43712µs my ( $i, $size ) = @_;
4381500ns my $results_hash;
439
440 # loop through the results
44112µs $results_hash->{'hits'} = $size;
4421400ns my $times;
44315µs if ( $offset + $results_per_page <= $size ) {
444 $times = $offset + $results_per_page;
445 }
446 else {
447 $times = $size;
448 }
449177µs for ( my $j = $offset ; $j < $times ; $j++ ) {
450259µs my $records_hash;
451259µs my $record;
452
453 ## Check if it's an index scan
4542533µ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 {
49025368µs5036.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;
49325113µs $results_hash->{'RECORDS'}[$j] = $record;
494 }
495
4961700ns }
49717µs $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
500119µs16µs if ( !$scan && $servers[ $i - 1 ] =~ /biblioserver/ ) {
# spent 6µs making 1 call to C4::Search::CORE:match
501
50214µs my $jmax =
503 $size > $facets_maxrecs ? $facets_maxrecs : $size;
50412µs for my $facet (@$facets) {
5057325µs for ( my $j = 0 ; $j < $jmax ; $j++ ) {
5061752.78ms35027.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();
508175152µs my @used_datas = ();
509175633µs foreach my $tag ( @{ $facet->{tags} } ) {
510
511 # avoid first line
512275385µs my $tag_num = substr( $tag, 0, 3 );
513275161µs my $letters = substr( $tag, 3 );
514275280µs my $field_pattern =
515 '\n' . $tag_num . ' ([^z][^\n]+)';
516275636µs $field_pattern = '\n' . $tag_num . ' ([^\n]+)'
517 if ( int($tag_num) < 10 );
5182758.14ms5505.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 );
5202751.01ms foreach my $field_token (@field_tokens) {
5212524.27ms2522.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 );
52325266µs my @values;
5242523.18ms for ( my $i = 0 ; $i < @subf ; $i += 2 ) {
525149616.1ms29928.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
526252369µs my $value = $subf[ $i + 1 ];
5272521.35ms252566µs $value =~ s/^ *//;
# spent 566µs making 252 calls to C4::Search::CORE:subst, avg 2µs/call
5282521.96ms2521.07ms $value =~ s/ *$//;
# spent 1.07ms making 252 calls to C4::Search::CORE:subst, avg 4µs/call
529252449µs push @values, $value;
530 }
53125296µs }
532252677µs my $data = join( $facet->{sep}, @values );
5332521.28ms unless ( $data ~~ @used_datas ) {
534216558µs $facets_counter->{ $facet->{idx} }
535 ->{$data}++;
536216201µs push @used_datas, $data;
537 }
538 } # fields
539 } # field codes
54074µs } # records
541735µs $facets_info->{ $facet->{idx} }->{label_value} =
542 $facet->{label};
543726µs $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
552122µs13µs if ( $servers[ $i - 1 ] =~ /biblioserver/ ) {
# spent 3µs making 1 call to C4::Search::CORE:match
553129µs115µ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 {
55851µs my $expandable;
55951µs my $number_of_facets;
56052µs my @this_facets_array;
5615327µs5247µ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 {
56915333µs $number_of_facets++;
570153165µ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
5772311µs my $facet_link_value = $one_facet;
57823176µ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,
581239µs my $facet_label_value = $one_facet;
5822373µs231.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;
5852327µs $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,
5922392µs2323µ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,
6092368µs2315µ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
6232346µs236µ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.
63023116µs 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
64558µs unless ( $facets_info->{$link_value}->{'expanded'} ) {
646 $expandable = 1
647 if ( ( $number_of_facets > 6 )
648 && ( $expanded_facet ne $link_value ) );
649 }
650565µs68µ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 }
671185µs1181ms );
# spent 181ms making 1 call to C4::Search::_ZOOM_event_loop
672170µs 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 {
77611µs my ( $operand, $index ) = @_;
7771800ns my ( @nontruncated, @righttruncated, @lefttruncated, @rightlefttruncated,
778 @regexpr );
77915µs11µs $operand =~ s/^ //g;
# spent 1µs making 1 call to C4::Search::CORE:subst
78014µs my @wordlist = split( /\s/, $operand );
78112µs 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 (
79916µs \@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
84613µs my ( $operand, $stemmed_operand, $index ) = @_;
84716µs19µs my $stemming = C4::Context->preference("QueryStemming") || 0;
# spent 9µs making 1 call to C4::Context::preference
84814µs13µs my $weight_fields = C4::Context->preference("QueryWeightFields") || 0;
# spent 3µs making 1 call to C4::Context::preference
84913µs13µs my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
# spent 3µs making 1 call to C4::Context::preference
850
85111µs my $weighted_query .= "(rk=("; # Specifies that we're applying rank
852
853 # Keyword, or, no index specified
85412µs if ( ( $index eq 'kw' ) || ( !$index ) ) {
85511µs $weighted_query .=
856 "Title-cover,ext,r1=\"$operand\""; # exact title-cover
8571900ns $weighted_query .= " or ti,ext,r2=\"$operand\""; # exact title
85811µs $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
8611400ns $weighted_query .= " or wrdl,fuzzy,r8=\"$operand\""
862 if $fuzzy_enabled; # add fuzzy, word list
8631500ns $weighted_query .= " or wrdl,right-Truncation,r9=\"$stemmed_operand\""
864 if ( $stemming and $stemmed_operand )
865 ; # add stemming, right truncation
86611µs $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
9011700ns $weighted_query .= "))"; # close rank specification
90216µs 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{
912169µ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
110418µs 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 {
116412µs my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
1165
116611µs my @operators = $operators ? @$operators : ();
11671700ns my @indexes = $indexes ? @$indexes : ();
116811µs my @operands = $operands ? @$operands : ();
11691600ns my @limits = $limits ? @$limits : ();
11701900ns my @sort_by = $sort_by ? @$sort_by : ();
1171
11721500ns my $query = $operands[0];
11731300ns my $index;
11741200ns my $term;
11751300ns my $query_desc;
1176
11771400ns my $QParser;
1178116µs22.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
1179116µs22µ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
118012µs undef $QParser if (scalar @limits > 0);
1181
118212µ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 {
12231230µs require Koha::QueryParser::Driver::PQF;
1224110µs118µs my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')';
# spent 18µs making 1 call to OpenILS::QueryParser::modifiers
1225235µs221µ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
1228110µs 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 {
124812µs my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
1249
125011µs warn "---------\nEnter buildQuery\n---------" if $DEBUG;
1251
12521600ns my $query_desc;
125317µs131.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
125612µs my @operators = $operators ? @$operators : ();
12571800ns my @indexes = $indexes ? @$indexes : ();
125811µs my @operands = $operands ? @$operands : ();
12591800ns my @limits = $limits ? @$limits : ();
126012µs my @sort_by = $sort_by ? @$sort_by : ();
1261
126219µs14.25ms my $stemming = C4::Context->preference("QueryStemming") || 0;
# spent 4.25ms making 1 call to C4::Context::preference
126317µs12.16ms my $auto_truncation = C4::Context->preference("QueryAutoTruncate") || 0;
# spent 2.16ms making 1 call to C4::Context::preference
126416µs12.53ms my $weight_fields = C4::Context->preference("QueryWeightFields") || 0;
# spent 2.53ms making 1 call to C4::Context::preference
126515µs12.11ms my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
# spent 2.11ms making 1 call to C4::Context::preference
126616µs12.63ms my $remove_stopwords = C4::Context->preference("QueryRemoveStopwords") || 0;
# spent 2.63ms making 1 call to C4::Context::preference
1267
126812µs my $query = $operands[0];
12691500ns my $simple_query = $operands[0];
1270
1271 # initialize the variables we're passing back
12721400ns my $query_cgi;
12731400ns my $query_type;
1274
12751200ns my $limit;
12761200ns my $limit_cgi;
12771300ns my $limit_desc;
1278
12791400ns my $stopwords_removed; # flag to determine if stopwords have been removed
1280
12811300ns my $cclq = 0;
128215µs174µs my $cclindexes = getIndexes();
# spent 74µs making 1 call to C4::Search::getIndexes
128318µs12µs if ( $query !~ /\s*ccl=/ ) {
# spent 2µs making 1 call to C4::Search::CORE:match
128415µs1400ns 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 }
12881600ns $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!!
129313µs1400ns 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 }
130314µs1600ns if ( $query =~ /^cql=/ ) {
# spent 600ns making 1 call to C4::Search::CORE:match
1304 return ( undef, $', $', "q=cql=".uri_escape($'), $', '', '', '', '', 'cql' );
1305 }
130614µ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 {
133311µs $query = ""
1334 ; # clear it out so we can populate properly with field-weighted, stemmed, etc. query
13351500ns 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
133914µs for ( my $i = 0 ; $i <= @operands ; $i++ ) {
1340
1341 # COMBINE OPERANDS, INDEXES AND OPERATORS
134222µs if ( $operands[$i] ) {
134318µs14µ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
13461500ns my $indexes_set;
1347
1348# If the user is sophisticated enough to specify an index, turn off field weighting, stemming, and stopword handling
134915µ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 {
135414µs11µs $operands[$i] =~ s/\?/{?}/g; # need to escape question marks
# spent 1µs making 1 call to C4::Search::CORE:subst
1355 }
13561700ns my $operand = $operands[$i];
13571800ns my $index = $indexes[$i];
1358
1359 # Add index-specific attributes
1360 # Date of Publication
136113µs 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
138311µs if(not $index){
1384 $index = 'kw';
1385 }
1386
1387 # Set default structure attribute (word list)
13881800ns my $struct_attr = q{};
138916µs12µ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
139412µs my $index_plus = $index . $struct_attr . ':';
139511µs my $index_plus_comma = $index . $struct_attr . ',';
1396
1397 # Remove Stopwords
13981600ns 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
14061500ns 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
14171600ns my $truncated_operand;
141816µs130µs my( $nontruncated, $righttruncated, $lefttruncated,
# spent 30µs making 1 call to C4::Search::_detect_truncation
1419 $rightlefttruncated, $regexpr
1420 ) = _detect_truncation( $operand, $index );
142111µs warn
1422"TRUNCATION: NON:>@$nontruncated< RIGHT:>@$righttruncated< LEFT:>@$lefttruncated< RIGHTLEFT:>@$rightlefttruncated< REGEX:>@$regexpr<"
1423 if $DEBUG;
1424
1425 # Apply Truncation
1426111µs 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 }
145517µs $operand = $truncated_operand if $truncated_operand;
145611µs warn "TRUNCATED OPERAND: >$truncated_operand<" if $DEBUG;
1457
1458 # Handle Stemming
14591800ns my $stemmed_operand;
14601700ns $stemmed_operand = _build_stemmed_operand($operand, $lang)
1461 if $stemming;
1462
14631400ns warn "STEMMED OPERAND: >$stemmed_operand<" if $DEBUG;
1464
1465 # Handle Field Weighting
14661400ns my $weighted_operand;
146711µs if ($weight_fields) {
146818µs156µs $weighted_operand = _build_weighted_query( $operand, $stemmed_operand, $index );
# spent 56µs making 1 call to C4::Search::_build_weighted_query
14691700ns $operand = $weighted_operand;
14701500ns $indexes_set = 1;
1471 }
1472
14731400ns warn "FIELD WEIGHTED OPERAND: >$weighted_operand<" if $DEBUG;
1474
1475 # If there's a previous operand, we need to add an operator
147612µ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
15051500ns $query .= " $index_plus " unless $indexes_set;
15061700ns $query .= $operand;
150712µs $query_desc .= " $index_plus $operands[$i]";
150816µs137µs $query_cgi .= "&idx=".uri_escape($index) if $index;
# spent 37µs making 1 call to URI::Escape::uri_escape
150913µs18µs $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i];
# spent 8µs making 1 call to URI::Escape::uri_escape
15101700ns $previous_operand = 1;
1511 }
1512 } #/if $operands
15131700ns } # /for
1514 }
15151400ns warn "QUERY BEFORE LIMITS: >$query<" if $DEBUG;
1516
1517 # add limits
15181500ns my %group_OR_limits;
15191500ns my $availability_limit;
152012µs 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 }
156813µs foreach my $k (keys (%group_OR_limits)) {
1569 $limit .= " and " if ( $query || $limit );
1570 $limit .= "($group_OR_limits{$k})";
1571 }
15721600ns 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;
158115µs12µs $query =~ s/(?<=(ti|au|pb|su|an|kw|mc|nb|ns)):/=/g;
# spent 2µs making 1 call to C4::Search::CORE:subst
158214µs1700ns $query =~ s/(?<=(wrdl)):/=/g;
# spent 700ns making 1 call to C4::Search::CORE:subst
158314µs1800ns $query =~ s/(?<=(trn|phr)):/=/g;
# spent 800ns making 1 call to C4::Search::CORE:subst
158414µs1800ns $limit =~ s/:/=/g;
# spent 800ns making 1 call to C4::Search::CORE:subst
158512µs for ( $query, $query_desc, $limit, $limit_desc ) {
158649µs42µs s/ +/ /g; # remove extra spaces
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 525ns/call
1587411µs45µs s/^ //g; # remove any beginning spaces
# spent 5µs making 4 calls to C4::Search::CORE:subst, avg 1µs/call
1588417µs41µs s/ $//g; # remove any ending spaces
# spent 1µs making 4 calls to C4::Search::CORE:subst, avg 375ns/call
1589411µs42µs s/==/=/g; # remove double == from query
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 475ns/call
1590 }
159114µs12µs $query_cgi =~ s/^&//; # remove unnecessary & from beginning of the query cgi
# spent 2µs making 1 call to C4::Search::CORE:subst
1592
159311µs 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
159711µs $query .= " " . $limit;
1598
1599 # Warnings if DEBUG
16001500ns 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 (
1610118µs 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 {
162913µs my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults ) = @_;
163016µs11.09ms my $dbh = C4::Context->dbh;
# spent 1.09ms making 1 call to C4::Context::dbh
16311400ns my @newresults;
1632
163314µs require C4::Items;
1634
163513µs $search_context = 'opac' if !$search_context || $search_context ne 'intranet';
163611µs my ($is_opac, $hidelostitems);
163712µs if ($search_context eq 'opac') {
1638110µs112µs $hidelostitems = C4::Context->preference('hidelostitems');
# spent 12µs making 1 call to C4::Context::preference
16391900ns $is_opac = 1;
1640 }
1641
1642 #Build branchnames hash
1643 #find branchname
1644 #get branch information.....
16451600ns my %branches;
1646124µ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
16471942µs1925µs $bsth->execute();
# spent 925µs making 1 call to DBI::st::execute
16481614µs33667µ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
1654110µs44.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)
165719µs41.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
16611600ns my %itemtypes;
1662151µ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 );
16661791µs4789µ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
16671879µs72974µ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
167418µs112.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
1677112µ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.76ms13.75ms $sth2->execute;
# spent 3.75ms making 1 call to DBI::st::execute
16791900ns my %subfieldstosearch;
16801297µs41175µs while ( ( my $column ) = $sth2->fetchrow ) {
# spent 175µs making 41 calls to DBI::st::fetchrow, avg 4µs/call
168140107µs40371µs my ( $tagfield, $tagsubfield ) =
# spent 371µs making 40 calls to C4::Biblio::GetMarcFromKohaField, avg 9µs/call
1682 &GetMarcFromKohaField( "items." . $column, "" );
16834058µs $subfieldstosearch{$column} = $tagsubfield;
1684 }
1685
1686 # handle which records to actually retrieve
16871400ns my $times;
168813µs 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
169515µs18µ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
169713µs18µ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
17001102µs for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) {
170125202µs25121ms my $marcrecord = MARC::File::USMARC::decode( $marcresults->[$i] );
# spent 121ms making 25 calls to MARC::File::USMARC::decode, avg 4.85ms/call
170225367µs12574.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));
170725175µs2531.0ms my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, $fw );
# spent 31.0ms making 25 calls to C4::Biblio::TransformMarcToKoha, avg 1.24ms/call
170825259µs10058.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
170925213µs10062.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
171025225µs10054.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
17112588µs $oldbiblio->{result_number} = $i + 1;
1712
1713 # add imageurl to itemtype if there is one
171425372µs252.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
171625298µs25139µ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
171725221µs259.00ms $oldbiblio->{normalized_upc} = GetNormalizedUPC( $marcrecord,$marcflavour);
# spent 9.00ms making 25 calls to C4::Koha::GetNormalizedUPC, avg 360µs/call
171825180µs257.68ms $oldbiblio->{normalized_ean} = GetNormalizedEAN( $marcrecord,$marcflavour);
# spent 7.68ms making 25 calls to C4::Koha::GetNormalizedEAN, avg 307µs/call
171925177µs257.58ms $oldbiblio->{normalized_oclc} = GetNormalizedOCLCNumber($marcrecord,$marcflavour);
# spent 7.58ms making 25 calls to C4::Koha::GetNormalizedOCLCNumber, avg 303µs/call
172025221µs256.88ms $oldbiblio->{normalized_isbn} = GetNormalizedISBN(undef,$marcrecord,$marcflavour);
# spent 6.88ms making 25 calls to C4::Koha::GetNormalizedISBN, avg 275µs/call
17212591µs $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
17242546µs $oldbiblio->{edition} = $oldbiblio->{editionstatement};
172525141µs $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
17282557µs 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
177725131µs257.00ms my @fields = $marcrecord->field($itemtag);
# spent 7.00ms making 25 calls to MARC::Record::field, avg 280µs/call
177825216µs25167µ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
17802523µs my $analyticsfield = '773';
17812545µs if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
1782 $analyticsfield = '773';
1783 } elsif ($marcflavor eq 'UNIMARC') {
1784 $analyticsfield = '461';
1785 }
178625184µs257.15ms foreach my $hostfield ( $marcrecord->field($analyticsfield)) {
# spent 7.15ms making 25 calls to MARC::Record::field, avg 286µs/call
1787972µs9199µs my $hostbiblionumber = $hostfield->subfield("0");
# spent 199µs making 9 calls to MARC::Field::subfield, avg 22µs/call
1788930µs9107µs my $linkeditemnumber = $hostfield->subfield("9");
# spent 107µs making 9 calls to MARC::Field::subfield, avg 12µs/call
1789927µs 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
18072516µs my @available_items_loop;
18082510µs my @onloan_items_loop;
18092510µs my @other_items_loop;
1810
1811258µs my $available_items;
1812257µs my $onloan_items;
18132510µs my $other_items;
1814
18152515µs my $ordered_count = 0;
1816259µs my $available_count = 0;
1817258µs my $onloan_count = 0;
1818259µs my $longoverdue_count = 0;
18192514µs my $other_count = 0;
18202512µs my $wthdrawn_count = 0;
18212518µs my $itemlost_count = 0;
18222513µs my $hideatopac_count = 0;
1823258µs my $itembinding_count = 0;
1824257µs my $itemdamaged_count = 0;
1825256µs my $item_in_transit_count = 0;
1826258µs my $can_place_holds = 0;
18272570µs my $item_onhold_count = 0;
18282523µs my $items_count = scalar(@fields);
182925141µs252.68ms my $maxitems_pref = C4::Context->preference('maxItemsinSearchResults');
# spent 2.68ms making 25 calls to C4::Context::preference, avg 107µs/call
18302585µs my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1;
18312517µs my @hiddenitems; # hidden itemnumbers based on OpacHiddenItems syspref
1832
1833 # loop through every item
18342565µs foreach my $field (@fields) {
18353214µs my $item;
1836
1837 # populate the items hash
183832554µs 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 }
184132170µs $item->{description} = $itemtypes{ $item->{itype} }{description};
1842
1843 # OPAC hidden items
18443251µs if ($is_opac) {
1845 # hidden because lost
18463224µs if ($hidelostitems && $item->{itemlost}) {
1847 $hideatopac_count++;
1848 next;
1849 }
1850 # hidden based on OpacHiddenItems syspref
185132235µs32125ms my @hi = C4::Items::GetHiddenItemnumbers($item);
# spent 125ms making 32 calls to C4::Items::GetHiddenItemnumbers, avg 3.90ms/call
18523244µs if (scalar @hi) {
1853 push @hiddenitems, @hi;
1854 $hideatopac_count++;
1855 next;
1856 }
1857 }
1858
185932325µs321.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
186032145µs32119µ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
186332290µs if ($item->{$hbranch}) {
1864 $item->{'branchname'} = $branches{$item->{$hbranch}};
1865 }
1866 elsif ($item->{$otherbranch}) { # Last resort
1867 $item->{'branchname'} = $branches{$item->{$otherbranch}};
1868 }
1869
187032294µs 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
187232187µs32302µs my $userenv = C4::Context->userenv;
# spent 302µs making 32 calls to C4::Context::userenv, avg 9µs/call
187332623µ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
189632134µs if ( $item->{notforloan} < 0 ) {
1897 $ordered_count++;
1898 }
1899
1900 # is item in transit?
19013230µs my $transfertwhen = '';
19023229µs my ($transfertfrom, $transfertto);
1903
1904 # is item on the reserve shelf?
19053225µs my $reservestatus = '';
1906
190732157µ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 #
192532290µs12876.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
192632359µs128118ms $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
193032336µs 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 {
19783228µs $can_place_holds = 1;
19793227µs $available_count++;
198032214µs $available_items->{$prefix}->{count}++ if $item->{$hbranch};
198132101µs foreach (qw(branchname itemcallnumber description)) {
198296326µs $available_items->{$prefix}->{$_} = $item->{$_};
1983 }
198432185µs $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} };
198532478µs322.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
19912550µs if ($items_count > 0 && $hideatopac_count == $items_count) {
1992 next;
1993 }
1994
19952539µs my ( $availableitemscount, $onloanitemscount, $otheritemscount );
199625446µs25168µ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 }
200025196µs2534µ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 }
200425244µs2537µs for my $key ( sort keys %$available_items ) {
# spent 37µs making 25 calls to C4::Search::CORE:sort, avg 1µs/call
20053028µs (++$availableitemscount > $maxitems) and last;
20062991µs 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
201125203µs25114ms SetUTF8Flag($marcrecord);
# spent 114ms making 25 calls to C4::Charset::SetUTF8Flag, avg 4.56ms/call
20122535µs warn $marcrecord->as_formatted if $DEBUG;
20132577µs my $interface = $search_context eq 'opac' ? 'OPAC' : '';
201425619µs1004.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
202025179µs25226µ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 }
20252514µs $oldbiblio->{norequests} = 1 unless $can_place_holds;
20262534µs $oldbiblio->{itemsplural} = 1 if $items_count > 1;
20272552µs $oldbiblio->{items_count} = $items_count;
20282565µs $oldbiblio->{available_items_loop} = \@available_items_loop;
20292553µs $oldbiblio->{onloan_items_loop} = \@onloan_items_loop;
20302539µs $oldbiblio->{other_items_loop} = \@other_items_loop;
20312577µs $oldbiblio->{availablecount} = $available_count;
20322517µs $oldbiblio->{availableplural} = 1 if $available_count > 1;
20332537µs $oldbiblio->{onloancount} = $onloan_count;
20342512µs $oldbiblio->{onloanplural} = 1 if $onloan_count > 1;
20352538µs $oldbiblio->{othercount} = $other_count;
20362514µs $oldbiblio->{otherplural} = 1 if $other_count > 1;
20372540µs $oldbiblio->{wthdrawncount} = $wthdrawn_count;
20382537µs $oldbiblio->{itemlostcount} = $itemlost_count;
20392532µs $oldbiblio->{damagedcount} = $itemdamaged_count;
20402533µs $oldbiblio->{intransitcount} = $item_in_transit_count;
20412531µs $oldbiblio->{onholdcount} = $item_onhold_count;
20422535µs $oldbiblio->{orderedcount} = $ordered_count;
2043
20442590µs25111µ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
2072252.32ms push( @newresults, $oldbiblio );
20731800ns }
2074
20751242µs 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 {
234612µs my ($zconns, $results, $callback) = @_;
2347152µs1267.7ms while ( ( my $i = ZOOM::event( $zconns ) ) != 0 ) {
# spent 67.7ms making 12 calls to ZOOM::event, avg 5.64ms/call
23481154µs11202µs my $ev = $zconns->[ $i - 1 ]->last_event();
# spent 202µs making 11 calls to ZOOM::Connection::last_event, avg 18µs/call
23491128µs1161µs if ( $ev == ZOOM::Event::ZEND ) {
# spent 61µs making 11 calls to ZOOM::Event::ZEND, avg 6µs/call
235012µs next unless $results->[ $i - 1 ];
235117µs171µs my $size = $results->[ $i - 1 ]->size();
# spent 71µs making 1 call to ZOOM::ResultSet::size
235218µs1113ms if ( $size > 0 ) {
2353 $callback->($i, $size);
2354 }
2355 }
2356 }
2357
235817µs 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