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

Filename/usr/share/koha/lib/C4/Search.pm
StatementsExecuted 12040 statements in 81.9ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
11127.4ms4.40sC4::Search::::searchResultsC4::Search::searchResults
11120.4ms75.9msC4::Search::::__ANON__[:670]C4::Search::__ANON__[:670]
1772316.54ms6.54msC4::Search::::CORE:regcompC4::Search::CORE:regcomp (opcode)
1114.94ms27.3msC4::Search::::parseQueryC4::Search::parseQuery
21091814.71ms4.71msC4::Search::::CORE:matchC4::Search::CORE:match (opcode)
1113.30ms6.33msC4::Search::::BEGIN@24C4::Search::BEGIN@24
1112.66ms9.92msC4::Search::::BEGIN@36C4::Search::BEGIN@36
1112.06ms11.1msC4::Search::::BEGIN@29C4::Search::BEGIN@29
5581911.49ms1.69msC4::Search::::CORE:substC4::Search::CORE:subst (opcode)
111919µs5.79msC4::Search::::BEGIN@34C4::Search::BEGIN@34
111846µs1.04msC4::Search::::BEGIN@25C4::Search::BEGIN@25
8151584µs584µsC4::Search::::CORE:sortC4::Search::CORE:sort (opcode)
111415µs37.8msC4::Search::::buildQueryC4::Search::buildQuery
111309µs143msC4::Search::::getRecordsC4::Search::getRecords
111274µs135msC4::Search::::_ZOOM_event_loopC4::Search::_ZOOM_event_loop
11162µs62µsC4::Search::::getIndexesC4::Search::getIndexes
11152µs55µsC4::Search::::_detect_truncationC4::Search::_detect_truncation
11134µs41µsC4::Search::::BEGIN@18C4::Search::BEGIN@18
11133µs44µsC4::Search::::_build_weighted_queryC4::Search::_build_weighted_query
11128µs178µsC4::Search::::BEGIN@2010C4::Search::BEGIN@2010
11126µs149µsC4::Search::::BEGIN@33C4::Search::BEGIN@33
11124µs45µsC4::Search::::BEGIN@37C4::Search::BEGIN@37
11119µs631µsC4::Search::::BEGIN@23C4::Search::BEGIN@23
11119µs54µsC4::Search::::BEGIN@27C4::Search::BEGIN@27
11119µs608µsC4::Search::::BEGIN@22C4::Search::BEGIN@22
11118µs68µsC4::Search::::BEGIN@35C4::Search::BEGIN@35
11118µs416µsC4::Search::::BEGIN@31C4::Search::BEGIN@31
11117µs23µsC4::Search::::BEGIN@39C4::Search::BEGIN@39
11116µs153µsC4::Search::::BEGIN@32C4::Search::BEGIN@32
11116µs53µsC4::Search::::BEGIN@28C4::Search::BEGIN@28
11115µs64µsC4::Search::::BEGIN@26C4::Search::BEGIN@26
11115µs19µsC4::Search::::BEGIN@21C4::Search::BEGIN@21
11114µs153µsC4::Search::::BEGIN@30C4::Search::BEGIN@30
11112µs112µsC4::Search::::BEGIN@40C4::Search::BEGIN@40
11111µs11µsC4::Search::::BEGIN@38C4::Search::BEGIN@38
1117µs7µsC4::Search::::BEGIN@43C4::Search::BEGIN@43
1115µs5µsC4::Search::::ENDC4::Search::END
0000s0sC4::Search::::AddSearchHistoryC4::Search::AddSearchHistory
0000s0sC4::Search::::FindDuplicateC4::Search::FindDuplicate
0000s0sC4::Search::::GetDistinctValuesC4::Search::GetDistinctValues
0000s0sC4::Search::::GetSearchHistoryC4::Search::GetSearchHistory
0000s0sC4::Search::::SearchAcquisitionsC4::Search::SearchAcquisitions
0000s0sC4::Search::::SimpleSearchC4::Search::SimpleSearch
0000s0sC4::Search::::__ANON__[:2327]C4::Search::__ANON__[:2327]
0000s0sC4::Search::::__ANON__[:297]C4::Search::__ANON__[:297]
0000s0sC4::Search::::_build_stemmed_operandC4::Search::_build_stemmed_operand
0000s0sC4::Search::::_handle_exploding_indexC4::Search::_handle_exploding_index
0000s0sC4::Search::::_remove_stopwordsC4::Search::_remove_stopwords
0000s0sC4::Search::::enabled_staff_search_viewsC4::Search::enabled_staff_search_views
0000s0sC4::Search::::pazGetRecordsC4::Search::pazGetRecords
0000s0sC4::Search::::z3950_search_argsC4::Search::z3950_search_args
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1package C4::Search;
2
3# This file is part of Koha.
4#
5# Koha is free software; you can redistribute it and/or modify it under the
6# terms of the GNU General Public License as published by the Free Software
7# Foundation; either version 2 of the License, or (at your option) any later
8# version.
9#
10# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along with
15# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
16# Suite 330, Boston, MA 02111-1307 USA
17
18348µs249µs
# spent 41µs (34+7) within C4::Search::BEGIN@18 which was called: # once (34µs+7µs) by main::BEGIN@48 at line 18
use strict;
# spent 41µs making 1 call to C4::Search::BEGIN@18 # spent 7µs making 1 call to strict::import
19#use warnings; FIXME - Bug 2505
2013µsrequire Exporter;
21331µs223µs
# spent 19µs (15+4) within C4::Search::BEGIN@21 which was called: # once (15µs+4µs) by main::BEGIN@48 at line 21
use C4::Context;
# spent 19µs making 1 call to C4::Search::BEGIN@21 # spent 4µs making 1 call to C4::Context::import
22345µs21.20ms
# spent 608µs (19+590) within C4::Search::BEGIN@22 which was called: # once (19µs+590µs) by main::BEGIN@48 at line 22
use C4::Biblio; # GetMarcFromKohaField, GetBiblioData
# spent 608µs making 1 call to C4::Search::BEGIN@22 # spent 590µs making 1 call to Exporter::import
23351µs21.24ms
# spent 631µs (19+611) within C4::Search::BEGIN@23 which was called: # once (19µs+611µs) by main::BEGIN@48 at line 23
use C4::Koha; # getFacets
# spent 631µs making 1 call to C4::Search::BEGIN@23 # spent 611µs making 1 call to Exporter::import
243221µs26.38ms
# spent 6.33ms (3.30+3.03) within C4::Search::BEGIN@24 which was called: # once (3.30ms+3.03ms) by main::BEGIN@48 at line 24
use Lingua::Stem;
# spent 6.33ms making 1 call to C4::Search::BEGIN@24 # spent 53µs making 1 call to Exporter::import
253188µs11.04ms
# spent 1.04ms (846µs+196µs) within C4::Search::BEGIN@25 which was called: # once (846µs+196µs) by main::BEGIN@48 at line 25
use C4::Search::PazPar2;
# spent 1.04ms making 1 call to C4::Search::BEGIN@25
26335µs276µs
# spent 64µs (15+49) within C4::Search::BEGIN@26 which was called: # once (15µs+49µs) by main::BEGIN@48 at line 26
use XML::Simple;
# spent 64µs making 1 call to C4::Search::BEGIN@26 # spent 12µs making 1 call to XML::Simple::import
27338µs289µs
# spent 54µs (19+35) within C4::Search::BEGIN@27 which was called: # once (19µs+35µs) by main::BEGIN@48 at line 27
use C4::Dates qw(format_date);
# spent 54µs making 1 call to C4::Search::BEGIN@27 # spent 35µs making 1 call to Exporter::import
28332µs290µs
# spent 53µs (16+37) within C4::Search::BEGIN@28 which was called: # once (16µs+37µs) by main::BEGIN@48 at line 28
use C4::Members qw(GetHideLostItemsPreference);
# spent 53µs making 1 call to C4::Search::BEGIN@28 # spent 37µs making 1 call to Exporter::import
293141µs211.3ms
# spent 11.1ms (2.06+9.08) within C4::Search::BEGIN@29 which was called: # once (2.06ms+9.08ms) by main::BEGIN@48 at line 29
use C4::XSLT;
# spent 11.1ms making 1 call to C4::Search::BEGIN@29 # spent 121µs making 1 call to Exporter::import
30332µs2293µs
# spent 153µs (14+139) within C4::Search::BEGIN@30 which was called: # once (14µs+139µs) by main::BEGIN@48 at line 30
use C4::Branch;
# spent 153µs making 1 call to C4::Search::BEGIN@30 # spent 140µs making 1 call to Exporter::import
31340µs2813µs
# spent 416µs (18+397) within C4::Search::BEGIN@31 which was called: # once (18µs+397µs) by main::BEGIN@48 at line 31
use C4::Reserves; # GetReserveStatus
# spent 416µs making 1 call to C4::Search::BEGIN@31 # spent 397µs making 1 call to Exporter::import
32354µs2289µs
# spent 153µs (16+137) within C4::Search::BEGIN@32 which was called: # once (16µs+137µs) by main::BEGIN@48 at line 32
use C4::Debug;
# spent 153µs making 1 call to C4::Search::BEGIN@32 # spent 137µs making 1 call to Exporter::import
33360µs2272µs
# spent 149µs (26+123) within C4::Search::BEGIN@33 which was called: # once (26µs+123µs) by main::BEGIN@48 at line 33
use C4::Charset;
# spent 149µs making 1 call to C4::Search::BEGIN@33 # spent 123µs making 1 call to Exporter::import
343170µs25.85ms
# spent 5.79ms (919µs+4.87) within C4::Search::BEGIN@34 which was called: # once (919µs+4.87ms) by main::BEGIN@48 at line 34
use YAML;
# spent 5.79ms making 1 call to C4::Search::BEGIN@34 # spent 54µs making 1 call to Exporter::import
35339µs2119µs
# spent 68µs (18+50) within C4::Search::BEGIN@35 which was called: # once (18µs+50µs) by main::BEGIN@48 at line 35
use URI::Escape;
# spent 68µs making 1 call to C4::Search::BEGIN@35 # spent 50µs making 1 call to Exporter::import
363167µs29.95ms
# spent 9.92ms (2.66+7.26) within C4::Search::BEGIN@36 which was called: # once (2.66ms+7.26ms) by main::BEGIN@48 at line 36
use Business::ISBN;
# spent 9.92ms making 1 call to C4::Search::BEGIN@36 # spent 33µs making 1 call to Exporter::import
37338µs267µs
# spent 45µs (24+21) within C4::Search::BEGIN@37 which was called: # once (24µs+21µs) by main::BEGIN@48 at line 37
use MARC::Record;
# spent 45µs making 1 call to C4::Search::BEGIN@37 # spent 21µs making 1 call to Exporter::import
38330µs111µs
# spent 11µs within C4::Search::BEGIN@38 which was called: # once (11µs+0s) by main::BEGIN@48 at line 38
use MARC::Field;
# spent 11µs making 1 call to C4::Search::BEGIN@38
39340µs228µs
# spent 23µs (17+6) within C4::Search::BEGIN@39 which was called: # once (17µs+6µs) by main::BEGIN@48 at line 39
use utf8;
# spent 23µs making 1 call to C4::Search::BEGIN@39 # spent 6µs making 1 call to utf8::import
40352µs2211µs
# spent 112µs (12+99) within C4::Search::BEGIN@40 which was called: # once (12µs+99µs) by main::BEGIN@48 at line 40
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG);
# spent 112µs making 1 call to C4::Search::BEGIN@40 # spent 99µs making 1 call to vars::import
41
42# set the version for version checking
43
# spent 7µs within C4::Search::BEGIN@43 which was called: # once (7µs+0s) by main::BEGIN@48 at line 46
BEGIN {
4411µs $VERSION = 3.07.00.049;
4517µs $DEBUG = ($ENV{DEBUG}) ? 1 : 0;
4619.19ms17µs}
# spent 7µs making 1 call to C4::Search::BEGIN@43
47
48=head1 NAME
49
- -
64125µs@ISA = qw(Exporter);
6516µs@EXPORT = qw(
66 &FindDuplicate
67 &SimpleSearch
68 &searchResults
69 &getRecords
70 &buildQuery
71 &AddSearchHistory
72 &GetDistinctValues
73 &enabled_staff_search_views
74 &SimpleSearch
75);
76
77# make all your functions, whether exported or not;
78
79=head2 FindDuplicate
80
- -
87sub FindDuplicate {
88 my ($record) = @_;
89 my $dbh = C4::Context->dbh;
90 my $result = TransformMarcToKoha( $dbh, $record, '' );
91 my $sth;
92 my $query;
93 my $search;
94 my $type;
95 my ( $biblionumber, $title );
96
97 # search duplicate on ISBN, easy and fast..
98 # ... normalize first
99 if ( $result->{isbn} ) {
100 $result->{isbn} =~ s/\(.*$//;
101 $result->{isbn} =~ s/\s+$//;
102 $query = "isbn:$result->{isbn}";
103 }
104 else {
105 my $QParser;
106 $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
107 my $titleindex;
108 my $authorindex;
109 my $op;
110
111 if ($QParser) {
112 $titleindex = 'title|exact';
113 $authorindex = 'author|exact';
114 $op = '&&';
115 $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
116 } else {
117 $titleindex = 'ti,ext';
118 $authorindex = 'au,ext';
119 $op = 'and';
120 }
121
122 $result->{title} =~ s /\\//g;
123 $result->{title} =~ s /\"//g;
124 $result->{title} =~ s /\(//g;
125 $result->{title} =~ s /\)//g;
126
127 # FIXME: instead of removing operators, could just do
128 # quotes around the value
129 $result->{title} =~ s/(and|or|not)//g;
130 $query = "$titleindex:\"$result->{title}\"";
131 if ( $result->{author} ) {
132 $result->{author} =~ s /\\//g;
133 $result->{author} =~ s /\"//g;
134 $result->{author} =~ s /\(//g;
135 $result->{author} =~ s /\)//g;
136
137 # remove valid operators
138 $result->{author} =~ s/(and|or|not)//g;
139 $query .= " $op $authorindex:\"$result->{author}\"";
140 }
141 }
142
143 my ( $error, $searchresults, undef ) = SimpleSearch($query); # FIXME :: hardcoded !
144 my @results;
145 if (!defined $error) {
146 foreach my $possible_duplicate_record (@{$searchresults}) {
147 my $marcrecord =
148 MARC::Record->new_from_usmarc($possible_duplicate_record);
149 my $result = TransformMarcToKoha( $dbh, $marcrecord, '' );
150
151 # FIXME :: why 2 $biblionumber ?
152 if ($result) {
153 push @results, $result->{'biblionumber'};
154 push @results, $result->{'title'};
155 }
156 }
157 }
158 return @results;
159}
160
161=head2 SimpleSearch
162
- -
223sub SimpleSearch {
224 my ( $query, $offset, $max_results, $servers ) = @_;
225
226 return ( 'No query entered', undef, undef ) unless $query;
227 # FIXME hardcoded value. See catalog/search.pl & opac-search.pl too.
228 my @servers = defined ( $servers ) ? @$servers : ( 'biblioserver' );
229 my @zoom_queries;
230 my @tmpresults;
231 my @zconns;
232 my $results = [];
233 my $total_hits = 0;
234
235 my $QParser;
236 $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') && ! ($query =~ m/\w,\w|\w=\w/));
237 if ($QParser) {
238 $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
239 }
240
241 # Initialize & Search Zebra
242 for ( my $i = 0 ; $i < @servers ; $i++ ) {
243 eval {
244 $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
245 if ($QParser) {
246 $query =~ s/=/:/g;
247 $QParser->parse( $query );
248 $query = $QParser->target_syntax($servers[$i]);
249 $zoom_queries[$i] = new ZOOM::Query::PQF( $query, $zconns[$i]);
250 } else {
251 $query =~ s/:/=/g;
252 $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]);
253 }
254 $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] );
255
256 # error handling
257 my $error =
258 $zconns[$i]->errmsg() . " ("
259 . $zconns[$i]->errcode() . ") "
260 . $zconns[$i]->addinfo() . " "
261 . $zconns[$i]->diagset();
262
263 return ( $error, undef, undef ) if $zconns[$i]->errcode();
264 };
265 if ($@) {
266
267 # caught a ZOOM::Exception
268 my $error =
269 $@->message() . " ("
270 . $@->code() . ") "
271 . $@->addinfo() . " "
272 . $@->diagset();
273 warn $error." for query: $query";
274 return ( $error, undef, undef );
275 }
276 }
277
278 _ZOOM_event_loop(
279 \@zconns,
280 \@tmpresults,
281 sub {
282 my ($i, $size) = @_;
283 my $first_record = defined($offset) ? $offset + 1 : 1;
284 my $hits = $tmpresults[ $i - 1 ]->size();
285 $total_hits += $hits;
286 my $last_record = $hits;
287 if ( defined $max_results && $offset + $max_results < $hits ) {
288 $last_record = $offset + $max_results;
289 }
290
291 for my $j ( $first_record .. $last_record ) {
292 my $record =
293 $tmpresults[ $i - 1 ]->record( $j - 1 )->raw()
294 ; # 0 indexed
295 push @{$results}, $record;
296 }
297 }
298 );
299
300 foreach my $zoom_query (@zoom_queries) {
301 $zoom_query->destroy();
302 }
303
304 return ( undef, $results, $total_hits );
305}
306
307=head2 getRecords
308
- -
323
# spent 143ms (309µs+143) within C4::Search::getRecords which was called: # once (309µs+143ms) by main::RUNTIME at line 523 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub getRecords {
324 my (
32514µ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
33012µs my @servers = @$servers_ref;
33111µs my @sort_by = @$sort_by_ref;
332
333 # Initialize variables for the ZOOM connection and results object
3341500ns my $zconn;
3351500ns my @zconns;
3361300ns my @results;
3371600ns my $results_hashref = ();
338
339 # Initialize variables for the faceted results objects
3401500ns my $facets_counter = ();
3411200ns my $facets_info = ();
34216µs11.94ms my $facets = getFacets();
# spent 1.94ms making 1 call to C4::Koha::getFacets
34317µs11.68ms my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets')||20;
# spent 1.68ms making 1 call to C4::Context::preference
344
3451700ns 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++ ) {
34917µs1889µs $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
# spent 889µs making 1 call to C4::Context::Zconn
350
351# perform the search, create the results objects
352# if this is a local search, use the $koha-query, if it's a federated one, use the federated-query
353119µs113µs my $query_to_use = ($servers[$i] =~ /biblioserver/) ? $koha_query : $simple_query;
# spent 13µs making 1 call to C4::Search::CORE:match
354
355 #$query_to_use = $simple_query if $scan;
35611µs warn $simple_query if ( $scan and $DEBUG );
357
358 # Check if we've got a query_type defined, if so, use it
35911µs eval {
36013µ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 {
374116µs23.40ms $results[$i] = $zconns[$i]->search(new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i]));
# spent 3.18ms making 1 call to ZOOM::Query::CCL2RPN::new # spent 213µs making 1 call to ZOOM::Connection::search
375 }
376 };
3771600ns 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;
38414µs foreach my $sort (@sort_by) {
38519µ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 {
4221500ns warn "Ignoring unrecognized sort '$sort' requested" if $sort_by;
423 }
424 }
4251800ns if ($sort_by && !$scan) {
426 if ( $results[$i]->sort( "yaz", $sort_by ) < 0 ) {
427 warn "WARNING sort $sort_by failed";
428 }
429 }
4301700ns } # finished looping through servers
431
432 # The big moment: asynchronously retrieve results from all servers
433 _ZOOM_event_loop(
434 \@zconns,
435 \@results,
436
# spent 75.9ms (20.4+55.5) within C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] which was called: # once (20.4ms+55.5ms) by C4::Search::_ZOOM_event_loop at line 2352
sub {
43711µs my ( $i, $size ) = @_;
4381400ns my $results_hash;
439
440 # loop through the results
44112µs $results_hash->{'hits'} = $size;
4421500ns my $times;
44315µs if ( $offset + $results_per_page <= $size ) {
444 $times = $offset + $results_per_page;
445 }
446 else {
447 $times = $size;
448 }
449132µs for ( my $j = $offset ; $j < $times ; $j++ ) {
450257µs my $records_hash;
451255µs my $record;
452
453 ## Check if it's an index scan
4542527µ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 {
49025242µs5016.5ms $record = $results[ $i - 1 ]->record($j)->raw();
# spent 14.0ms making 25 calls to ZOOM::ResultSet::record, avg 561µs/call # spent 2.49ms making 25 calls to ZOOM::Record::raw, avg 100µs/call
491
492 # warn "RECORD $j:".$record;
4932580µs $results_hash->{'RECORDS'}[$j] = $record;
494 }
495
4961600ns }
49716µ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
500120µs17µs if ( !$scan && $servers[ $i - 1 ] =~ /biblioserver/ ) {
# spent 7µs making 1 call to C4::Search::CORE:match
501
50215µs my $jmax =
503 $size > $facets_maxrecs ? $facets_maxrecs : $size;
50412µs for my $facet (@$facets) {
5057174µs for ( my $j = 0 ; $j < $jmax ; $j++ ) {
5061751.38ms35023.4ms my $render_record =
# spent 13.5ms making 175 calls to ZOOM::ResultSet::record, avg 77µs/call # spent 9.96ms making 175 calls to ZOOM::Record::render, avg 57µs/call
507 $results[ $i - 1 ]->record($j)->render();
508175120µs my @used_datas = ();
509175464µs foreach my $tag ( @{ $facet->{tags} } ) {
510
511 # avoid first line
512275241µs my $tag_num = substr( $tag, 0, 3 );
513275113µs my $letters = substr( $tag, 3 );
514275211µs my $field_pattern =
515 '\n' . $tag_num . ' ([^z][^\n]+)';
516275290µs $field_pattern = '\n' . $tag_num . ' ([^\n]+)'
517 if ( int($tag_num) < 10 );
5182755.29ms5503.62ms my @field_tokens =
# spent 1.94ms making 275 calls to C4::Search::CORE:regcomp, avg 7µs/call # spent 1.68ms making 275 calls to C4::Search::CORE:match, avg 6µs/call
519 ( $render_record =~ /$field_pattern/g );
520275602µs foreach my $field_token (@field_tokens) {
5212523.12ms2521.75ms my @subf = ( $field_token =~
# spent 1.75ms making 252 calls to C4::Search::CORE:match, avg 7µs/call
522 /\$([a-zA-Z0-9]) ([^\$]+)/g );
52325272µs my @values;
5242521.80ms for ( my $i = 0 ; $i < @subf ; $i += 2 ) {
525149611.6ms29925.74ms if ( $letters =~ $subf[$i] ) {
# spent 4.58ms making 1496 calls to C4::Search::CORE:regcomp, avg 3µs/call # spent 1.16ms making 1496 calls to C4::Search::CORE:match, avg 778ns/call
526252243µs my $value = $subf[ $i + 1 ];
5272521.02ms252398µs $value =~ s/^ *//;
# spent 398µs making 252 calls to C4::Search::CORE:subst, avg 2µs/call
5282521.43ms252886µs $value =~ s/ *$//;
# spent 886µs making 252 calls to C4::Search::CORE:subst, avg 4µs/call
529252358µs push @values, $value;
530 }
53125296µs }
532252461µs my $data = join( $facet->{sep}, @values );
533252894µs unless ( $data ~~ @used_datas ) {
534216652µs $facets_counter->{ $facet->{idx} }
535 ->{$data}++;
536216193µ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};
543725µ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
552128µs13µs if ( $servers[ $i - 1 ] =~ /biblioserver/ ) {
# spent 3µs making 1 call to C4::Search::CORE:match
553131µ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 {
55852µs my $expandable;
55952µs my $number_of_facets;
56052µs my @this_facets_array;
5615387µs5285µs for my $one_facet (
# spent 285µs making 5 calls to C4::Search::CORE:sort, avg 57µs/call
562 sort {
563 $facets_counter->{$link_value}
564 ->{$b} <=> $facets_counter->{$link_value}
565 ->{$a}
566 } keys %{ $facets_counter->{$link_value} }
567 )
568 {
56915349µs $number_of_facets++;
570153252µ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
5772316µs my $facet_link_value = $one_facet;
57823244µs24570µs $facet_link_value =~ s/[()!?¡¿؟]/ /g;
# spent 368µs making 23 calls to C4::Search::CORE:subst, avg 16µs/call # spent 202µs making 1 call to utf8::SWASHNEW
579
580 # fix the length that will display in the label,
5812315µs my $facet_label_value = $one_facet;
58223112µs232.39ms my $facet_max_length = C4::Context->preference(
# spent 2.39ms making 23 calls to C4::Context::preference, avg 104µs/call
583 'FacetLabelTruncationLength')
584 || 20;
5852339µ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,
59223131µs2333µs if ( $link_value =~ /branch/ ) {
# spent 33µs making 23 calls to C4::Search::CORE:match, avg 1µs/call
593 if ( defined $branches
594 && ref($branches) eq "HASH"
595 && defined $branches->{$one_facet}
596 && ref( $branches->{$one_facet} ) eq
597 "HASH" )
598 {
599 $facet_label_value =
600 $branches->{$one_facet}
601 ->{'branchname'};
602 }
603 else {
604 $facet_label_value = "*";
605 }
606 }
607
608 # if it's a itemtype, label by the name, not the code,
60923104µs2325µs if ( $link_value =~ /itype/ ) {
# spent 25µs making 23 calls to C4::Search::CORE:match, avg 1µs/call
610 if ( defined $itemtypes
611 && ref($itemtypes) eq "HASH"
612 && defined $itemtypes->{$one_facet}
613 && ref( $itemtypes->{$one_facet} ) eq
614 "HASH" )
615 {
616 $facet_label_value =
617 $itemtypes->{$one_facet}
618 ->{'description'};
619 }
620 }
621
622 # also, if it's a location code, use the name instead of the code
6232364µs238µs if ( $link_value =~ /location/ ) {
# spent 8µs making 23 calls to C4::Search::CORE:match, avg 361ns/call
624 $facet_label_value =
625 GetKohaAuthorisedValueLib( 'LOC',
626 $one_facet, $opac );
627 }
628
629 # but we're down with the whole label being in the link's title.
63023158µ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
645512µs unless ( $facets_info->{$link_value}->{'expanded'} ) {
646 $expandable = 1
647 if ( ( $number_of_facets > 6 )
648 && ( $expanded_facet ne $link_value ) );
649 }
6505126µs617µs push @facets_loop,
# spent 9µs making 5 calls to C4::Search::CORE:match, avg 2µs/call # spent 8µs making 1 call to C4::Context::preference
651 {
652 type_link_value => $link_value,
653 type_id => $link_value . "_id",
654 "type_label_"
655 . $facets_info->{$link_value}->{'label_value'} =>
656 1,
657 facets => \@this_facets_array,
658 expandable => $expandable,
659 expand => $link_value,
660 }
661 unless (
662 (
663 $facets_info->{$link_value}->{'label_value'} =~
664 /Libraries/
665 )
666 and ( C4::Context->preference('singleBranchMode') )
667 );
668 }
669 }
670 }
6711107µs1135ms );
# spent 135ms making 1 call to C4::Search::_ZOOM_event_loop
6721113µ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 55µs (52+3) within C4::Search::_detect_truncation which was called: # once (52µs+3µs) by C4::Search::buildQuery at line 1418
sub _detect_truncation {
77612µs my ( $operand, $index ) = @_;
77712µs my ( @nontruncated, @righttruncated, @lefttruncated, @rightlefttruncated,
778 @regexpr );
77915µs1600ns $operand =~ s/^ //g;
# spent 600ns making 1 call to C4::Search::CORE:subst
78014µs my @wordlist = split( /\s/, $operand );
78115µs foreach my $word (@wordlist) {
782131µs33µs if ( $word =~ s/^\*([^\*]+)\*$/$1/ ) {
# spent 3µs making 3 calls to C4::Search::CORE:subst, avg 867ns/call
783 push @rightlefttruncated, $word;
784 }
785 elsif ( $word =~ s/^\*([^\*]+)$/$1/ ) {
786 push @lefttruncated, $word;
787 }
788 elsif ( $word =~ s/^([^\*]+)\*$/$1/ ) {
789 push @righttruncated, $word;
790 }
791 elsif ( index( $word, "*" ) < 0 ) {
792 push @nontruncated, $word;
793 }
794 else {
795 push @regexpr, $word;
796 }
797 }
798 return (
799113µ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 44µs (33+11) within C4::Search::_build_weighted_query which was called: # once (33µs+11µs) by C4::Search::buildQuery at line 1468
sub _build_weighted_query {
843
844# FIELD WEIGHTING - This is largely experimental stuff. What I'm committing works
845# pretty well but could work much better if we had a smarter query parser
84612µs my ( $operand, $stemmed_operand, $index ) = @_;
84716µs16µs my $stemming = C4::Context->preference("QueryStemming") || 0;
# spent 6µs making 1 call to C4::Context::preference
84813µs13µs my $weight_fields = C4::Context->preference("QueryWeightFields") || 0;
# spent 3µs making 1 call to C4::Context::preference
84912µs12µs my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
# spent 2µs making 1 call to C4::Context::preference
850
8511900ns my $weighted_query .= "(rk=("; # Specifies that we're applying rank
852
853 # Keyword, or, no index specified
85411µs if ( ( $index eq 'kw' ) || ( !$index ) ) {
85511µs $weighted_query .=
856 "Title-cover,ext,r1=\"$operand\""; # exact title-cover
8571800ns $weighted_query .= " or ti,ext,r2=\"$operand\""; # exact title
8581900ns $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
8631300ns $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
9011400ns $weighted_query .= "))"; # close rank specification
90216µs return $weighted_query;
903}
904
905=head2 getIndexes
906
- -
911
# spent 62µs within C4::Search::getIndexes which was called: # once (62µs+0s) by C4::Search::buildQuery at line 1282
sub getIndexes{
912152µ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
1104111µ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 27.3ms (4.94+22.4) within C4::Search::parseQuery which was called: # once (4.94ms+22.4ms) by C4::Search::buildQuery at line 1253
sub parseQuery {
116412µs my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
1165
116611µs my @operators = $operators ? @$operators : ();
116711µs my @indexes = $indexes ? @$indexes : ();
116812µs my @operands = $operands ? @$operands : ();
11691600ns my @limits = $limits ? @$limits : ();
11701900ns my @sort_by = $sort_by ? @$sort_by : ();
1171
11721600ns my $query = $operands[0];
11731300ns my $index;
11741200ns my $term;
11751200ns my $query_desc;
1176
11771300ns my $QParser;
1178120µs23.36ms $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') || $query =~ s/^qp=//);
# spent 3.35ms making 1 call to C4::Context::preference # spent 3µs making 1 call to C4::Search::CORE:subst
1179112µs21µs undef $QParser if ($query =~ m/^(ccl=|pqf=|cql=)/ || grep (/\w,\w|\w=\w/, @operands, @indexes) );
# spent 1µs making 2 calls to C4::Search::CORE:match, avg 650ns/call
118012µs undef $QParser if (scalar @limits > 0);
1181
118211µ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 {
12231173µs require Koha::QueryParser::Driver::PQF;
1224116µs112µs my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')';
# spent 12µs making 1 call to OpenILS::QueryParser::modifiers
1225234µs219µs s/$modifier_re//g for @operands;
# spent 17µs making 1 call to C4::Search::CORE:regcomp # spent 2µs making 1 call to C4::Search::CORE:subst
1226 }
1227
1228110µs return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc);
1229}
1230
1231=head2 buildQuery
1232
- -
1247
# spent 37.8ms (415µs+37.4) within C4::Search::buildQuery which was called: # once (415µs+37.4ms) by main::RUNTIME at line 453 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub buildQuery {
124812µs my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
1249
125012µs warn "---------\nEnter buildQuery\n---------" if $DEBUG;
1251
12521300ns my $query_desc;
125317µs127.3ms ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang);
# spent 27.3ms making 1 call to C4::Search::parseQuery
1254
1255 # dereference
125612µs my @operators = $operators ? @$operators : ();
125711µs my @indexes = $indexes ? @$indexes : ();
125811µs my @operands = $operands ? @$operands : ();
12591800ns my @limits = $limits ? @$limits : ();
126011µs my @sort_by = $sort_by ? @$sort_by : ();
1261
1262110µs12.22ms my $stemming = C4::Context->preference("QueryStemming") || 0;
# spent 2.22ms making 1 call to C4::Context::preference
126317µs12.01ms my $auto_truncation = C4::Context->preference("QueryAutoTruncate") || 0;
# spent 2.01ms making 1 call to C4::Context::preference
126415µs12.04ms my $weight_fields = C4::Context->preference("QueryWeightFields") || 0;
# spent 2.04ms making 1 call to C4::Context::preference
126517µs11.87ms my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0;
# spent 1.87ms making 1 call to C4::Context::preference
126617µs11.68ms my $remove_stopwords = C4::Context->preference("QueryRemoveStopwords") || 0;
# spent 1.68ms making 1 call to C4::Context::preference
1267
126813µs my $query = $operands[0];
126912µs my $simple_query = $operands[0];
1270
1271 # initialize the variables we're passing back
12721700ns my $query_cgi;
12731400ns my $query_type;
1274
12751500ns my $limit;
12761200ns my $limit_cgi;
12771300ns my $limit_desc;
1278
12791400ns my $stopwords_removed; # flag to determine if stopwords have been removed
1280
12811700ns my $cclq = 0;
1282110µs162µs my $cclindexes = getIndexes();
# spent 62µ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
1284115µ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 }
12881700ns $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!!
129314µ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µs1400ns if ( $query =~ /^pqf=/ ) {
# spent 400ns making 1 call to C4::Search::CORE:match
1307 if ($query_desc) {
1308 $query_cgi = "q=".uri_escape($query_desc);
1309 } else {
1310 $query_desc = $';
1311 $query_cgi = "q=pqf=".uri_escape($');
1312 }
1313 return ( undef, $', $', $query_cgi, $query_desc, '', '', '', '', 'pqf' );
1314 }
1315
1316 # pass nested queries directly
1317 # FIXME: need better handling of some of these variables in this case
1318 # Nested queries aren't handled well and this implementation is flawed and causes users to be
1319 # unable to search for anything containing () commenting out, will be rewritten for 3.4.0
1320# if ( $query =~ /(\(|\))/ ) {
1321# return (
1322# undef, $query, $simple_query, $query_cgi,
1323# $query, $limit, $limit_cgi, $limit_desc,
1324# $stopwords_removed, 'ccl'
1325# );
1326# }
1327
1328# Form-based queries are non-nested and fixed depth, so we can easily modify the incoming
1329# query operands and indexes and add stemming, truncation, field weighting, etc.
1330# Once we do so, we'll end up with a value in $query, just like if we had an
1331# incoming $query from the user
1332 else {
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] ) {
1343125µs19µs $operands[$i]=~s/^\s+//;
# spent 9µs making 1 call to C4::Search::CORE:subst
1344
1345 # A flag to determine whether or not to add the index to the query
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 {
135418µs13µs $operands[$i] =~ s/\?/{?}/g; # need to escape question marks
# spent 3µs making 1 call to C4::Search::CORE:subst
1355 }
13561700ns my $operand = $operands[$i];
135711µs my $index = $indexes[$i];
1358
1359 # Add index-specific attributes
1360 # Date of Publication
136114µ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)
138811µs my $struct_attr = q{};
138919µs14µs unless ( $indexes_set || !$index || $index =~ /(st-|phr|ext|wrdl|nb|ns)/ ) {
# spent 4µs making 1 call to C4::Search::CORE:match
1390 $struct_attr = ",wrdl";
1391 }
1392
1393 # Some helpful index variants
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
14171500ns my $truncated_operand;
141816µs155µs my( $nontruncated, $righttruncated, $lefttruncated,
# spent 55µs making 1 call to C4::Search::_detect_truncation
1419 $rightlefttruncated, $regexpr
1420 ) = _detect_truncation( $operand, $index );
142112µs warn
1422"TRUNCATION: NON:>@$nontruncated< RIGHT:>@$righttruncated< LEFT:>@$lefttruncated< RIGHTLEFT:>@$rightlefttruncated< REGEX:>@$regexpr<"
1423 if $DEBUG;
1424
1425 # Apply Truncation
142614µ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 }
14551800ns $operand = $truncated_operand if $truncated_operand;
14561500ns warn "TRUNCATED OPERAND: >$truncated_operand<" if $DEBUG;
1457
1458 # Handle Stemming
14591600ns my $stemmed_operand;
14601500ns $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) {
146815µs144µs $weighted_operand = _build_weighted_query( $operand, $stemmed_operand, $index );
# spent 44µs making 1 call to C4::Search::_build_weighted_query
14691800ns $operand = $weighted_operand;
14701600ns $indexes_set = 1;
1471 }
1472
14731600ns warn "FIELD WEIGHTED OPERAND: >$weighted_operand<" if $DEBUG;
1474
1475 # If there's a previous operand, we need to add an operator
147613µ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
15051400ns $query .= " $index_plus " unless $indexes_set;
15061800ns $query .= $operand;
150712µs $query_desc .= " $index_plus $operands[$i]";
150817µs156µs $query_cgi .= "&idx=".uri_escape($index) if $index;
# spent 56µ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
15131500ns } # /for
1514 }
15151700ns warn "QUERY BEFORE LIMITS: >$query<" if $DEBUG;
1516
1517 # add limits
15181800ns my %group_OR_limits;
15191700ns my $availability_limit;
1520165µ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 }
1568114µs foreach my $k (keys (%group_OR_limits)) {
1569 $limit .= " and " if ( $query || $limit );
1570 $limit .= "($group_OR_limits{$k})";
1571 }
15721800ns 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;
1581114µ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
158215µs11µs $query =~ s/(?<=(wrdl)):/=/g;
# spent 1µs making 1 call to C4::Search::CORE:subst
1583110µs1600ns $query =~ s/(?<=(trn|phr)):/=/g;
# spent 600ns making 1 call to C4::Search::CORE:subst
158414µs1900ns $limit =~ s/:/=/g;
# spent 900ns making 1 call to C4::Search::CORE:subst
158513µs for ( $query, $query_desc, $limit, $limit_desc ) {
1586410µs42µs s/ +/ /g; # remove extra spaces
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 550ns/call
1587414µs47µs s/^ //g; # remove any beginning spaces
# spent 7µs making 4 calls to C4::Search::CORE:subst, avg 2µs/call
158849µs42µs s/ $//g; # remove any ending spaces
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 375ns/call
1589410µs42µs s/==/=/g; # remove double == from query
# spent 2µs making 4 calls to C4::Search::CORE:subst, avg 400ns/call
1590 }
159116µ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) {
159427µs21µs s/"//g;
# spent 1µs making 2 calls to C4::Search::CORE:subst, avg 500ns/call
1595 }
1596 # append the limit to the query
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 (
1610123µ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 4.40s (27.4ms+4.37) within C4::Search::searchResults which was called: # once (27.4ms+4.37s) by main::RUNTIME at line 560 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
sub searchResults {
162916µs my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults ) = @_;
1630111µs11.02ms my $dbh = C4::Context->dbh;
# spent 1.02ms making 1 call to C4::Context::dbh
16311500ns my @newresults;
1632
163313µs require C4::Items;
1634
163513µs $search_context = 'opac' if !$search_context || $search_context ne 'intranet';
16361800ns my ($is_opac, $hidelostitems);
163712µs if ($search_context eq 'opac') {
163818µs111µs $hidelostitems = C4::Context->preference('hidelostitems');
# spent 11µs making 1 call to C4::Context::preference
16391700ns $is_opac = 1;
1640 }
1641
1642 #Build branchnames hash
1643 #find branchname
1644 #get branch information.....
16451300ns my %branches;
1646123µs2195µs my $bsth =$dbh->prepare("SELECT branchcode,branchname FROM branches"); # FIXME : use C4::Branch::GetBranches
# spent 105µs making 1 call to DBI::db::prepare # spent 90µs making 1 call to DBD::mysql::db::prepare
164711.70ms11.69ms $bsth->execute();
# spent 1.69ms making 1 call to DBI::st::execute
16481250µs33280µs while ( my $bdata = $bsth->fetchrow_hashref ) {
# spent 197µs making 11 calls to DBI::st::fetchrow_hashref, avg 18µs/call # spent 42µs making 11 calls to DBI::st::fetch, avg 4µs/call # spent 40µs making 11 calls to DBI::common::FETCH, avg 4µs/call
1649 $branches{ $bdata->{'branchcode'} } = $bdata->{'branchname'};
1650 }
1651# FIXME - We build an authorised values hash here, using the default framework
1652# though it is possible to have different authvals for different fws.
1653
165417µs44.59ms my $shelflocations =GetKohaAuthorisedValues('items.location','');
# spent 4.59ms making 1 call to C4::Koha::GetKohaAuthorisedValues # spent 7µs making 2 calls to DBI::common::DESTROY, avg 4µs/call # spent 2µs making 1 call to DBD::_mem::common::DESTROY
1655
1656 # get notforloan authorised value list (see $shelflocations FIXME)
165715µs42.13ms my $notforloan_authorised_value = GetAuthValCode('items.notforloan','');
# spent 2.12ms making 1 call to C4::Koha::GetAuthValCode # spent 9µs making 2 calls to DBI::common::DESTROY, avg 4µs/call # spent 2µs making 1 call to DBD::_mem::common::DESTROY
1658
1659 #Build itemtype hash
1660 #find itemtype & itemtype image
16611700ns my %itemtypes;
1662141µs2140µs $bsth =
# spent 76µs making 1 call to DBI::db::prepare # spent 65µs making 1 call to DBD::mysql::db::prepare
1663 $dbh->prepare(
1664 "SELECT itemtype,description,imageurl,summary,notforloan FROM itemtypes"
1665 );
166611.03ms41.03ms $bsth->execute();
# spent 1.02ms making 1 call to DBI::st::execute # spent 7µs making 2 calls to DBI::common::DESTROY, avg 4µs/call # spent 3µs making 1 call to DBD::_mem::common::DESTROY
166711.27ms721.39ms while ( my $bdata = $bsth->fetchrow_hashref ) {
# spent 1.06ms making 24 calls to DBI::st::fetchrow_hashref, avg 44µs/call # spent 174µs making 24 calls to DBI::st::fetch, avg 7µs/call # spent 154µs making 24 calls to DBI::common::FETCH, avg 6µs/call
1668 foreach (qw(description imageurl summary notforloan)) {
166992291µs $itemtypes{ $bdata->{'itemtype'} }->{$_} = $bdata->{$_};
1670 }
1671 }
1672
1673 #search item field code
1674112µs15.92ms my ($itemtag, undef) = &GetMarcFromKohaField( "items.itemnumber", "" );
# spent 5.92ms making 1 call to C4::Biblio::GetMarcFromKohaField
1675
1676 ## find column names of items related to MARC
1677127µs2236µs my $sth2 = $dbh->prepare("SHOW COLUMNS FROM items");
# spent 128µs making 1 call to DBI::db::prepare # spent 108µs making 1 call to DBD::mysql::db::prepare
167814.64ms14.60ms $sth2->execute;
# spent 4.60ms making 1 call to DBI::st::execute
16791900ns my %subfieldstosearch;
16801437µs41268µs while ( ( my $column ) = $sth2->fetchrow ) {
# spent 268µs making 41 calls to DBI::st::fetchrow, avg 7µs/call
168140122µs40602µs my ( $tagfield, $tagsubfield ) =
# spent 602µs making 40 calls to C4::Biblio::GetMarcFromKohaField, avg 15µs/call
1682 &GetMarcFromKohaField( "items." . $column, "" );
16834077µs $subfieldstosearch{$column} = $tagsubfield;
1684 }
1685
1686 # handle which records to actually retrieve
16871400ns my $times;
168817µ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
169517µs111µs my $marcflavour = C4::Context->preference("marcflavour");
# spent 11µs making 1 call to C4::Context::preference
1696 # We get the biblionumber position in MARC
169714µs111µs my ($bibliotag,$bibliosubf)=GetMarcFromKohaField('biblio.biblionumber','');
# spent 11µs making 1 call to C4::Biblio::GetMarcFromKohaField
1698
1699 # loop through all of the records we've retrieved
17001116µs for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) {
170125241µs25138ms my $marcrecord = MARC::File::USMARC::decode( $marcresults->[$i] );
# spent 138ms making 25 calls to MARC::File::USMARC::decode, avg 5.51ms/call
170225394µs12576.2ms my $fw = $scan
# spent 67.9ms making 25 calls to C4::Biblio::GetFrameworkCode, avg 2.72ms/call # spent 7.83ms making 25 calls to MARC::Record::subfield, avg 313µs/call # spent 385µs making 50 calls to DBI::common::DESTROY, avg 8µs/call # spent 102µs making 25 calls to DBD::_mem::common::DESTROY, avg 4µs/call
1703 ? undef
1704 : $bibliotag < 10
1705 ? GetFrameworkCode($marcrecord->field($bibliotag)->data)
1706 : GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf));
170725187µs2547.9ms my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, $fw );
# spent 47.9ms making 25 calls to C4::Biblio::TransformMarcToKoha, avg 1.91ms/call
170825244µs10057.7ms $oldbiblio->{subtitle} = GetRecordValue('subtitle', $marcrecord, $fw);
# spent 57.4ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.30ms/call # spent 250µs making 50 calls to DBI::common::DESTROY, avg 5µs/call # spent 76µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call
170925207µs10055.9ms $oldbiblio->{source_t} = GetRecordValue('source_t', $marcrecord, $fw);
# spent 55.5ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.22ms/call # spent 260µs making 50 calls to DBI::common::DESTROY, avg 5µs/call # spent 66µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call
171025219µs10053.1ms $oldbiblio->{source_g} = GetRecordValue('source_g', $marcrecord, $fw);
# spent 52.8ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.11ms/call # spent 229µs making 50 calls to DBI::common::DESTROY, avg 5µs/call # spent 63µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call
17112586µs $oldbiblio->{result_number} = $i + 1;
1712
1713 # add imageurl to itemtype if there is one
171425427µs252.03ms $oldbiblio->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} );
# spent 2.03ms making 25 calls to C4::Koha::getitemtypeimagelocation, avg 81µs/call
1715
171625204µs25134µs $oldbiblio->{'authorised_value_images'} = ($search_context eq 'opac' && C4::Context->preference('AuthorisedValueImages')) || ($search_context eq 'intranet' && C4::Context->preference('StaffAuthorisedValueImages')) ? C4::Items::get_authorised_value_images( C4::Biblio::get_biblio_authorised_values( $oldbiblio->{'biblionumber'}, $marcrecord ) ) : [];
# spent 134µs making 25 calls to C4::Context::preference, avg 5µs/call
171725217µs259.42ms $oldbiblio->{normalized_upc} = GetNormalizedUPC( $marcrecord,$marcflavour);
# spent 9.42ms making 25 calls to C4::Koha::GetNormalizedUPC, avg 377µs/call
171825199µs257.21ms $oldbiblio->{normalized_ean} = GetNormalizedEAN( $marcrecord,$marcflavour);
# spent 7.21ms making 25 calls to C4::Koha::GetNormalizedEAN, avg 289µs/call
171925174µs257.71ms $oldbiblio->{normalized_oclc} = GetNormalizedOCLCNumber($marcrecord,$marcflavour);
# spent 7.71ms making 25 calls to C4::Koha::GetNormalizedOCLCNumber, avg 308µs/call
172025167µs256.83ms $oldbiblio->{normalized_isbn} = GetNormalizedISBN(undef,$marcrecord,$marcflavour);
# spent 6.83ms making 25 calls to C4::Koha::GetNormalizedISBN, avg 273µs/call
17212588µ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
17242582µs $oldbiblio->{edition} = $oldbiblio->{editionstatement};
172525147µ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
17282568µ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
177725160µs256.26ms my @fields = $marcrecord->field($itemtag);
# spent 6.26ms making 25 calls to MARC::Record::field, avg 250µs/call
177825126µs25200µs my $marcflavor = C4::Context->preference("marcflavour");
# spent 200µs making 25 calls to C4::Context::preference, avg 8µs/call
1779 # adding linked items that belong to host records
17802519µs my $analyticsfield = '773';
17812547µs if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
1782 $analyticsfield = '773';
1783 } elsif ($marcflavor eq 'UNIMARC') {
1784 $analyticsfield = '461';
1785 }
178625109µs256.14ms foreach my $hostfield ( $marcrecord->field($analyticsfield)) {
# spent 6.14ms making 25 calls to MARC::Record::field, avg 246µs/call
1787926µs9180µs my $hostbiblionumber = $hostfield->subfield("0");
# spent 180µs making 9 calls to MARC::Field::subfield, avg 20µs/call
1788927µs9113µs my $linkeditemnumber = $hostfield->subfield("9");
# spent 113µs making 9 calls to MARC::Field::subfield, avg 13µ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
18072513µs my @available_items_loop;
1808258µs my @onloan_items_loop;
1809259µs my @other_items_loop;
1810
18112511µs my $available_items;
1812258µs my $onloan_items;
1813258µs my $other_items;
1814
18152514µs my $ordered_count = 0;
1816259µs my $available_count = 0;
1817259µs my $onloan_count = 0;
1818259µs my $longoverdue_count = 0;
18192511µs my $other_count = 0;
18202516µs my $wthdrawn_count = 0;
18212514µs my $itemlost_count = 0;
18222512µs my $hideatopac_count = 0;
1823259µs my $itembinding_count = 0;
18242511µs my $itemdamaged_count = 0;
1825257µs my $item_in_transit_count = 0;
18262544µs my $can_place_holds = 0;
18272512µs my $item_onhold_count = 0;
18282521µs my $items_count = scalar(@fields);
182925126µs252.17ms my $maxitems_pref = C4::Context->preference('maxItemsinSearchResults');
# spent 2.17ms making 25 calls to C4::Context::preference, avg 87µs/call
18302585µs my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1;
18312518µs my @hiddenitems; # hidden itemnumbers based on OpacHiddenItems syspref
1832
1833 # loop through every item
183425128µs foreach my $field (@fields) {
18353216µs my $item;
1836
1837 # populate the items hash
183832608µs foreach my $code ( keys %subfieldstosearch ) {
183912807.29ms128071.0ms $item->{$code} = $field->subfield( $subfieldstosearch{$code} );
# spent 71.0ms making 1280 calls to MARC::Field::subfield, avg 55µs/call
1840 }
184132193µs $item->{description} = $itemtypes{ $item->{itype} }{description};
1842
1843 # OPAC hidden items
18443254µs if ($is_opac) {
1845 # hidden because lost
18463229µs if ($hidelostitems && $item->{itemlost}) {
1847 $hideatopac_count++;
1848 next;
1849 }
1850 # hidden based on OpacHiddenItems syspref
185132276µs32122ms my @hi = C4::Items::GetHiddenItemnumbers($item);
# spent 122ms making 32 calls to C4::Items::GetHiddenItemnumbers, avg 3.81ms/call
18523248µs if (scalar @hi) {
1853 push @hiddenitems, @hi;
1854 $hideatopac_count++;
1855 next;
1856 }
1857 }
1858
185932435µs322.46ms my $hbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'homebranch' : 'holdingbranch';
# spent 2.46ms making 32 calls to C4::Context::preference, avg 77µs/call
186032133µs32104µs my $otherbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'holdingbranch' : 'homebranch';
# spent 104µs making 32 calls to C4::Context::preference, avg 3µs/call
1861
1862 # set item's branch name, use HomeOrHoldingBranch syspref first, fall back to the other one
186332234µ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
187032275µ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
187232168µs32368µs my $userenv = C4::Context->userenv;
# spent 368µs making 32 calls to C4::Context::userenv, avg 11µs/call
187332624µ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
189632181µs if ( $item->{notforloan} < 0 ) {
1897 $ordered_count++;
1898 }
1899
1900 # is item in transit?
19013240µs my $transfertwhen = '';
19023233µs my ($transfertfrom, $transfertto);
1903
1904 # is item on the reserve shelf?
19053230µs my $reservestatus = '';
1906
190732203µ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 #
192532321µs12881.0ms ($transfertwhen, $transfertfrom, $transfertto) = C4::Circulation::GetTransfers($item->{itemnumber});
# spent 80.5ms making 32 calls to C4::Circulation::GetTransfers, avg 2.52ms/call # spent 389µs making 64 calls to DBI::common::DESTROY, avg 6µs/call # spent 113µs making 32 calls to DBD::_mem::common::DESTROY, avg 4µs/call
192632416µs128122ms $reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber}, $oldbiblio->{biblionumber} );
# spent 122ms making 32 calls to C4::Reserves::GetReserveStatus, avg 3.80ms/call # spent 400µs making 64 calls to DBI::common::DESTROY, avg 6µs/call # spent 79µs making 32 calls to DBD::_mem::common::DESTROY, avg 2µs/call
1927 }
1928
1929 # item is withdrawn, lost, damaged, not for loan, reserved or in transit
193032389µ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 {
19783233µs $can_place_holds = 1;
19793230µs $available_count++;
198032248µs $available_items->{$prefix}->{count}++ if $item->{$hbranch};
198132116µs foreach (qw(branchname itemcallnumber description)) {
198296322µs $available_items->{$prefix}->{$_} = $item->{$_};
1983 }
198432180µs $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} };
198532564µs322.60ms $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} );
# spent 2.60ms making 32 calls to C4::Koha::getitemtypeimagelocation, avg 81µs/call
1986 }
1987 }
1988 } # notforloan, item level and biblioitem level
1989
1990 # if all items are hidden, do not show the record
19912558µs if ($items_count > 0 && $hideatopac_count == $items_count) {
1992 next;
1993 }
1994
19952527µs my ( $availableitemscount, $onloanitemscount, $otheritemscount );
199625480µs25221µs for my $key ( sort keys %$onloan_items ) {
# spent 221µs making 25 calls to C4::Search::CORE:sort, avg 9µs/call
1997 (++$onloanitemscount > $maxitems) and last;
1998 push @onloan_items_loop, $onloan_items->{$key};
1999 }
200025162µs2527µs for my $key ( sort keys %$other_items ) {
# spent 27µs making 25 calls to C4::Search::CORE:sort, avg 1µs/call
2001 (++$otheritemscount > $maxitems) and last;
2002 push @other_items_loop, $other_items->{$key};
2003 }
200425205µs2535µs for my $key ( sort keys %$available_items ) {
# spent 35µs making 25 calls to C4::Search::CORE:sort, avg 1µs/call
20053027µs (++$availableitemscount > $maxitems) and last;
20062987µs push @available_items_loop, $available_items->{$key}
2007 }
2008
2009 # XSLT processing of some stuff
201032.26ms2327µs
# spent 178µs (28+149) within C4::Search::BEGIN@2010 which was called: # once (28µs+149µs) by main::BEGIN@48 at line 2010
use C4::Charset;
# spent 178µs making 1 call to C4::Search::BEGIN@2010 # spent 149µs making 1 call to Exporter::import
201125287µs25112ms SetUTF8Flag($marcrecord);
# spent 112ms making 25 calls to C4::Charset::SetUTF8Flag, avg 4.48ms/call
20122541µs warn $marcrecord->as_formatted if $DEBUG;
201325378µs my $interface = $search_context eq 'opac' ? 'OPAC' : '';
201425616µs1003.36s if (!$scan && C4::Context->preference($interface . "XSLTResultsDisplay")) {
# spent 3.36s making 25 calls to C4::XSLT::XSLTParse4Display, avg 134ms/call # spent 1.38ms making 50 calls to XML::LibXML::Node::DESTROY, avg 28µs/call # spent 338µs making 25 calls to C4::Context::preference, avg 14µs/call
2015 $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, $interface."XSLTResultsDisplay", 1, \@hiddenitems);
2016 # the last parameter tells Koha to clean up the problematic ampersand entities that Zebra outputs
2017 }
2018
2019 # if biblio level itypes are used and itemtype is notforloan, it can't be reserved either
202025192µs25225µs if (!C4::Context->preference("item-level_itypes")) {
# spent 225µs making 25 calls to C4::Context::preference, avg 9µs/call
2021 if ($itemtypes{ $oldbiblio->{itemtype} }->{notforloan}) {
2022 $can_place_holds = 0;
2023 }
2024 }
20252517µs $oldbiblio->{norequests} = 1 unless $can_place_holds;
20262533µs $oldbiblio->{itemsplural} = 1 if $items_count > 1;
20272563µs $oldbiblio->{items_count} = $items_count;
202825124µs $oldbiblio->{available_items_loop} = \@available_items_loop;
20292568µs $oldbiblio->{onloan_items_loop} = \@onloan_items_loop;
203025150µs $oldbiblio->{other_items_loop} = \@other_items_loop;
20312554µs $oldbiblio->{availablecount} = $available_count;
20322517µs $oldbiblio->{availableplural} = 1 if $available_count > 1;
20332546µs $oldbiblio->{onloancount} = $onloan_count;
20342513µs $oldbiblio->{onloanplural} = 1 if $onloan_count > 1;
20352555µs $oldbiblio->{othercount} = $other_count;
20362516µs $oldbiblio->{otherplural} = 1 if $other_count > 1;
20372551µs $oldbiblio->{wthdrawncount} = $wthdrawn_count;
20382533µs $oldbiblio->{itemlostcount} = $itemlost_count;
20392539µs $oldbiblio->{damagedcount} = $itemdamaged_count;
20402539µs $oldbiblio->{intransitcount} = $item_in_transit_count;
20412535µs $oldbiblio->{onholdcount} = $item_onhold_count;
20422539µs $oldbiblio->{orderedcount} = $ordered_count;
2043
20442597µs25118µs if (C4::Context->preference("AlternateHoldingsField") && $items_count == 0) {
# spent 118µs making 25 calls to C4::Context::preference, avg 5µs/call
2045 my $fieldspec = C4::Context->preference("AlternateHoldingsField");
2046 my $subfields = substr $fieldspec, 3;
2047 my $holdingsep = C4::Context->preference("AlternateHoldingsSeparator") || ' ';
2048 my @alternateholdingsinfo = ();
2049 my @holdingsfields = $marcrecord->field(substr $fieldspec, 0, 3);
2050 my $alternateholdingscount = 0;
2051
2052 for my $field (@holdingsfields) {
2053 my %holding = ( holding => '' );
2054 my $havesubfield = 0;
2055 for my $subfield ($field->subfields()) {
2056 if ((index $subfields, $$subfield[0]) >= 0) {
2057 $holding{'holding'} .= $holdingsep if (length $holding{'holding'} > 0);
2058 $holding{'holding'} .= $$subfield[1];
2059 $havesubfield++;
2060 }
2061 }
2062 if ($havesubfield) {
2063 push(@alternateholdingsinfo, \%holding);
2064 $alternateholdingscount++;
2065 }
2066 }
2067
2068 $oldbiblio->{'ALTERNATEHOLDINGS'} = \@alternateholdingsinfo;
2069 $oldbiblio->{'alternateholdings_count'} = $alternateholdingscount;
2070 }
2071
2072252.41ms push( @newresults, $oldbiblio );
20731700ns }
2074
20751185µ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 135ms (274µs+135) within C4::Search::_ZOOM_event_loop which was called: # once (274µs+135ms) by C4::Search::getRecords at line 671
sub _ZOOM_event_loop {
2346157µs my ($zconns, $results, $callback) = @_;
2347163µs1258.2ms while ( ( my $i = ZOOM::event( $zconns ) ) != 0 ) {
# spent 58.2ms making 12 calls to ZOOM::event, avg 4.85ms/call
23481170µs11206µs my $ev = $zconns->[ $i - 1 ]->last_event();
# spent 206µs making 11 calls to ZOOM::Connection::last_event, avg 19µs/call
23491132µs1171µs if ( $ev == ZOOM::Event::ZEND ) {
# spent 71µs making 11 calls to ZOOM::Event::ZEND, avg 6µs/call
235011µs next unless $results->[ $i - 1 ];
235115µs146µs my $size = $results->[ $i - 1 ]->size();
# spent 46µs making 1 call to ZOOM::ResultSet::size
235217µs175.9ms if ( $size > 0 ) {
2353 $callback->($i, $size);
2354 }
2355 }
2356 }
2357
2358111µs foreach my $result (@$results) {
2359111µs1110µs $result->destroy();
# spent 110µs making 1 call to ZOOM::ResultSet::destroy
2360 }
2361}
2362
2363
2364162µs
# spent 5µs within C4::Search::END which was called: # once (5µs+0s) by main::RUNTIME at line 0 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl
END { } # module clean-up code here (global destructor)
2365
2366117µs1;
2367__END__
 
# spent 4.71ms within C4::Search::CORE:match which was called 2109 times, avg 2µs/call: # 1496 times (1.16ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 525, avg 778ns/call # 275 times (1.68ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 518, avg 6µs/call # 252 times (1.75ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 521, avg 7µs/call # 23 times (33µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 592, avg 1µs/call # 23 times (25µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 609, avg 1µs/call # 23 times (8µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 623, avg 361ns/call # 5 times (9µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 650, avg 2µs/call # 2 times (1µs+0s) by C4::Search::parseQuery at line 1179, avg 650ns/call # once (13µs+0s) by C4::Search::getRecords at line 353 # once (7µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 500 # once (4µs+0s) by C4::Search::buildQuery at line 1389 # once (3µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 552 # once (2µs+0s) by C4::Search::buildQuery at line 1283 # once (600ns+0s) by C4::Search::buildQuery at line 1303 # once (500ns+0s) by C4::Search::buildQuery at line 1349 # once (400ns+0s) by C4::Search::buildQuery at line 1284 # once (400ns+0s) by C4::Search::buildQuery at line 1293 # once (400ns+0s) by C4::Search::buildQuery at line 1306
sub C4::Search::CORE:match; # opcode
# spent 6.54ms within C4::Search::CORE:regcomp which was called 1772 times, avg 4µs/call: # 1496 times (4.58ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 525, avg 3µs/call # 275 times (1.94ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 518, avg 7µs/call # once (17µs+0s) by C4::Search::parseQuery at line 1225
sub C4::Search::CORE:regcomp; # opcode
# spent 584µs within C4::Search::CORE:sort which was called 81 times, avg 7µs/call: # 25 times (221µs+0s) by C4::Search::searchResults at line 1996, avg 9µs/call # 25 times (35µs+0s) by C4::Search::searchResults at line 2004, avg 1µs/call # 25 times (27µs+0s) by C4::Search::searchResults at line 2000, avg 1µs/call # 5 times (285µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 561, avg 57µs/call # once (15µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 553
sub C4::Search::CORE:sort; # opcode
# spent 1.69ms (1.49+202µs) within C4::Search::CORE:subst which was called 558 times, avg 3µs/call: # 252 times (886µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 528, avg 4µs/call # 252 times (398µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 527, avg 2µs/call # 23 times (167µs+202µs) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 578, avg 16µs/call # 4 times (7µs+0s) by C4::Search::buildQuery at line 1587, avg 2µs/call # 4 times (2µs+0s) by C4::Search::buildQuery at line 1586, avg 550ns/call # 4 times (2µs+0s) by C4::Search::buildQuery at line 1589, avg 400ns/call # 4 times (2µs+0s) by C4::Search::buildQuery at line 1588, avg 375ns/call # 3 times (3µs+0s) by C4::Search::_detect_truncation at line 782, avg 867ns/call # 2 times (1µs+0s) by C4::Search::buildQuery at line 1594, avg 500ns/call # once (9µs+0s) by C4::Search::buildQuery at line 1343 # once (3µs+0s) by C4::Search::parseQuery at line 1178 # once (3µs+0s) by C4::Search::buildQuery at line 1354 # once (2µs+0s) by C4::Search::parseQuery at line 1225 # once (2µs+0s) by C4::Search::buildQuery at line 1581 # once (2µs+0s) by C4::Search::buildQuery at line 1591 # once (1µs+0s) by C4::Search::buildQuery at line 1582 # once (900ns+0s) by C4::Search::buildQuery at line 1584 # once (600ns+0s) by C4::Search::buildQuery at line 1583 # once (600ns+0s) by C4::Search::_detect_truncation at line 779
sub C4::Search::CORE:subst; # opcode