Filename | /usr/share/koha/lib/C4/Search.pm |
Statements | Executed 12040 statements in 91.9ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 28.8ms | 113ms | __ANON__[:670] | C4::Search::
1 | 1 | 1 | 25.5ms | 5.46s | searchResults | C4::Search::
1772 | 3 | 1 | 9.98ms | 9.98ms | CORE:regcomp (opcode) | C4::Search::
2109 | 18 | 1 | 6.40ms | 6.40ms | CORE:match (opcode) | C4::Search::
1 | 1 | 1 | 5.02ms | 31.6ms | parseQuery | C4::Search::
1 | 1 | 1 | 4.70ms | 8.93ms | BEGIN@24 | C4::Search::
1 | 1 | 1 | 3.39ms | 19.3ms | BEGIN@29 | C4::Search::
1 | 1 | 1 | 2.89ms | 10.1ms | BEGIN@36 | C4::Search::
558 | 19 | 1 | 1.78ms | 1.96ms | CORE:subst (opcode) | C4::Search::
1 | 1 | 1 | 1.51ms | 1.77ms | BEGIN@25 | C4::Search::
1 | 1 | 1 | 929µs | 5.78ms | BEGIN@34 | C4::Search::
81 | 5 | 1 | 501µs | 501µs | CORE:sort (opcode) | C4::Search::
1 | 1 | 1 | 287µs | 45.8ms | buildQuery | C4::Search::
1 | 1 | 1 | 275µs | 190ms | getRecords | C4::Search::
1 | 1 | 1 | 196µs | 181ms | _ZOOM_event_loop | C4::Search::
1 | 1 | 1 | 74µs | 74µs | getIndexes | C4::Search::
1 | 1 | 1 | 41µs | 56µs | _build_weighted_query | C4::Search::
1 | 1 | 1 | 36µs | 129µs | BEGIN@26 | C4::Search::
1 | 1 | 1 | 33µs | 42µs | BEGIN@18 | C4::Search::
1 | 1 | 1 | 29µs | 824µs | BEGIN@23 | C4::Search::
1 | 1 | 1 | 29µs | 1.36ms | BEGIN@22 | C4::Search::
1 | 1 | 1 | 28µs | 30µs | _detect_truncation | C4::Search::
1 | 1 | 1 | 28µs | 100µs | BEGIN@28 | C4::Search::
1 | 1 | 1 | 26µs | 93µs | BEGIN@27 | C4::Search::
1 | 1 | 1 | 25µs | 314µs | BEGIN@30 | C4::Search::
1 | 1 | 1 | 24µs | 133µs | BEGIN@2010 | C4::Search::
1 | 1 | 1 | 22µs | 27µs | BEGIN@21 | C4::Search::
1 | 1 | 1 | 22µs | 44µs | BEGIN@37 | C4::Search::
1 | 1 | 1 | 17µs | 22µs | BEGIN@39 | C4::Search::
1 | 1 | 1 | 16µs | 88µs | BEGIN@33 | C4::Search::
1 | 1 | 1 | 15µs | 105µs | BEGIN@32 | C4::Search::
1 | 1 | 1 | 15µs | 19µs | BEGIN@38 | C4::Search::
1 | 1 | 1 | 15µs | 69µs | BEGIN@35 | C4::Search::
1 | 1 | 1 | 14µs | 370µs | BEGIN@31 | C4::Search::
1 | 1 | 1 | 11µs | 99µs | BEGIN@40 | C4::Search::
1 | 1 | 1 | 7µs | 7µs | BEGIN@43 | C4::Search::
1 | 1 | 1 | 6µs | 6µs | END | C4::Search::
0 | 0 | 0 | 0s | 0s | AddSearchHistory | C4::Search::
0 | 0 | 0 | 0s | 0s | FindDuplicate | C4::Search::
0 | 0 | 0 | 0s | 0s | GetDistinctValues | C4::Search::
0 | 0 | 0 | 0s | 0s | GetSearchHistory | C4::Search::
0 | 0 | 0 | 0s | 0s | SearchAcquisitions | C4::Search::
0 | 0 | 0 | 0s | 0s | SimpleSearch | C4::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:2327] | C4::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:297] | C4::Search::
0 | 0 | 0 | 0s | 0s | _build_stemmed_operand | C4::Search::
0 | 0 | 0 | 0s | 0s | _handle_exploding_index | C4::Search::
0 | 0 | 0 | 0s | 0s | _remove_stopwords | C4::Search::
0 | 0 | 0 | 0s | 0s | enabled_staff_search_views | C4::Search::
0 | 0 | 0 | 0s | 0s | pazGetRecords | C4::Search::
0 | 0 | 0 | 0s | 0s | z3950_search_args | C4::Search::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package 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 | |||||
18 | 3 | 62µs | 2 | 52µs | # spent 42µs (33+9) within C4::Search::BEGIN@18 which was called:
# once (33µs+9µs) by main::BEGIN@48 at line 18 # spent 42µs making 1 call to C4::Search::BEGIN@18
# spent 9µs making 1 call to strict::import |
19 | #use warnings; FIXME - Bug 2505 | ||||
20 | 1 | 2µs | require Exporter; | ||
21 | 3 | 55µs | 2 | 32µs | # spent 27µs (22+5) within C4::Search::BEGIN@21 which was called:
# once (22µs+5µs) by main::BEGIN@48 at line 21 # spent 27µs making 1 call to C4::Search::BEGIN@21
# spent 5µs making 1 call to C4::Context::import |
22 | 3 | 68µs | 2 | 2.69ms | # spent 1.36ms (29µs+1.33) within C4::Search::BEGIN@22 which was called:
# once (29µs+1.33ms) by main::BEGIN@48 at line 22 # spent 1.36ms making 1 call to C4::Search::BEGIN@22
# spent 1.33ms making 1 call to Exporter::import |
23 | 3 | 70µs | 2 | 1.62ms | # spent 824µs (29+795) within C4::Search::BEGIN@23 which was called:
# once (29µs+795µs) by main::BEGIN@48 at line 23 # spent 824µs making 1 call to C4::Search::BEGIN@23
# spent 795µs making 1 call to Exporter::import |
24 | 3 | 245µs | 2 | 8.99ms | # spent 8.93ms (4.70+4.23) within C4::Search::BEGIN@24 which was called:
# once (4.70ms+4.23ms) by main::BEGIN@48 at line 24 # spent 8.93ms making 1 call to C4::Search::BEGIN@24
# spent 58µs making 1 call to Exporter::import |
25 | 3 | 255µs | 2 | 1.78ms | # spent 1.77ms (1.51+264µs) within C4::Search::BEGIN@25 which was called:
# once (1.51ms+264µs) by main::BEGIN@48 at line 25 # spent 1.77ms making 1 call to C4::Search::BEGIN@25
# spent 4µs making 1 call to UNIVERSAL::import |
26 | 3 | 63µs | 2 | 149µs | # spent 129µs (36+93) within C4::Search::BEGIN@26 which was called:
# once (36µs+93µs) by main::BEGIN@48 at line 26 # spent 129µs making 1 call to C4::Search::BEGIN@26
# spent 20µs making 1 call to XML::Simple::import |
27 | 3 | 73µs | 2 | 161µs | # spent 93µs (26+68) within C4::Search::BEGIN@27 which was called:
# once (26µs+68µs) by main::BEGIN@48 at line 27 # spent 93µs making 1 call to C4::Search::BEGIN@27
# spent 68µs making 1 call to Exporter::import |
28 | 3 | 67µs | 2 | 172µs | # spent 100µs (28+72) within C4::Search::BEGIN@28 which was called:
# once (28µs+72µs) by main::BEGIN@48 at line 28 # spent 100µs making 1 call to C4::Search::BEGIN@28
# spent 72µs making 1 call to Exporter::import |
29 | 3 | 213µs | 2 | 19.5ms | # spent 19.3ms (3.39+15.9) within C4::Search::BEGIN@29 which was called:
# once (3.39ms+15.9ms) by main::BEGIN@48 at line 29 # spent 19.3ms making 1 call to C4::Search::BEGIN@29
# spent 207µs making 1 call to Exporter::import |
30 | 3 | 43µs | 2 | 602µs | # spent 314µs (25+288) within C4::Search::BEGIN@30 which was called:
# once (25µs+288µs) by main::BEGIN@48 at line 30 # spent 314µs making 1 call to C4::Search::BEGIN@30
# spent 288µs making 1 call to Exporter::import |
31 | 3 | 38µs | 2 | 726µs | # spent 370µs (14+356) within C4::Search::BEGIN@31 which was called:
# once (14µs+356µs) by main::BEGIN@48 at line 31 # spent 370µs making 1 call to C4::Search::BEGIN@31
# spent 356µs making 1 call to Exporter::import |
32 | 3 | 36µs | 2 | 195µs | # spent 105µs (15+90) within C4::Search::BEGIN@32 which was called:
# once (15µs+90µs) by main::BEGIN@48 at line 32 # spent 105µs making 1 call to C4::Search::BEGIN@32
# spent 90µs making 1 call to Exporter::import |
33 | 3 | 35µs | 2 | 160µs | # spent 88µs (16+72) within C4::Search::BEGIN@33 which was called:
# once (16µs+72µs) by main::BEGIN@48 at line 33 # spent 88µs making 1 call to C4::Search::BEGIN@33
# spent 72µs making 1 call to Exporter::import |
34 | 3 | 162µs | 2 | 5.82ms | # spent 5.78ms (929µs+4.85) within C4::Search::BEGIN@34 which was called:
# once (929µs+4.85ms) by main::BEGIN@48 at line 34 # spent 5.78ms making 1 call to C4::Search::BEGIN@34
# spent 42µs making 1 call to Exporter::import |
35 | 3 | 41µs | 2 | 123µs | # spent 69µs (15+54) within C4::Search::BEGIN@35 which was called:
# once (15µs+54µs) by main::BEGIN@48 at line 35 # spent 69µs making 1 call to C4::Search::BEGIN@35
# spent 54µs making 1 call to Exporter::import |
36 | 3 | 142µs | 2 | 10.1ms | # spent 10.1ms (2.89+7.21) within C4::Search::BEGIN@36 which was called:
# once (2.89ms+7.21ms) by main::BEGIN@48 at line 36 # spent 10.1ms making 1 call to C4::Search::BEGIN@36
# spent 26µs making 1 call to Exporter::import |
37 | 3 | 39µs | 2 | 65µs | # spent 44µs (22+21) within C4::Search::BEGIN@37 which was called:
# once (22µs+21µs) by main::BEGIN@48 at line 37 # spent 44µs making 1 call to C4::Search::BEGIN@37
# spent 21µs making 1 call to Exporter::import |
38 | 3 | 36µs | 2 | 23µs | # spent 19µs (15+4) within C4::Search::BEGIN@38 which was called:
# once (15µs+4µs) by main::BEGIN@48 at line 38 # spent 19µs making 1 call to C4::Search::BEGIN@38
# spent 4µs making 1 call to UNIVERSAL::import |
39 | 3 | 40µs | 2 | 27µs | # spent 22µs (17+5) within C4::Search::BEGIN@39 which was called:
# once (17µs+5µs) by main::BEGIN@48 at line 39 # spent 22µs making 1 call to C4::Search::BEGIN@39
# spent 5µs making 1 call to utf8::import |
40 | 3 | 53µs | 2 | 188µs | # spent 99µs (11+88) within C4::Search::BEGIN@40 which was called:
# once (11µs+88µs) by main::BEGIN@48 at line 40 # spent 99µs making 1 call to C4::Search::BEGIN@40
# spent 88µs making 1 call to vars::import |
41 | |||||
42 | # set the version for version checking | ||||
43 | # spent 7µs within C4::Search::BEGIN@43 which was called:
# once (7µs+0s) by main::BEGIN@48 at line 46 | ||||
44 | 2 | 8µs | $VERSION = 3.07.00.049; | ||
45 | $DEBUG = ($ENV{DEBUG}) ? 1 : 0; | ||||
46 | 1 | 9.05ms | 1 | 7µs | } # spent 7µs making 1 call to C4::Search::BEGIN@43 |
47 | |||||
48 | =head1 NAME | ||||
49 | |||||
- - | |||||
64 | 1 | 14µs | @ISA = qw(Exporter); | ||
65 | 1 | 4µ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 | |||||
- - | |||||
87 | sub 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 | |||||
- - | |||||
223 | sub SimpleSearch { | ||||
224 | my ( $query, $offset, $max_results, $servers ) = @_; | ||||
225 | |||||
226 | return ( 'No query entered', undef, undef ) unless $query; | ||||
227 | # FIXME hardcoded value. See catalog/search.pl & opac-search.pl too. | ||||
228 | my @servers = defined ( $servers ) ? @$servers : ( 'biblioserver' ); | ||||
229 | my @zoom_queries; | ||||
230 | my @tmpresults; | ||||
231 | my @zconns; | ||||
232 | my $results = []; | ||||
233 | my $total_hits = 0; | ||||
234 | |||||
235 | my $QParser; | ||||
236 | $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') && ! ($query =~ m/\w,\w|\w=\w/)); | ||||
237 | if ($QParser) { | ||||
238 | $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate'); | ||||
239 | } | ||||
240 | |||||
241 | # Initialize & Search Zebra | ||||
242 | for ( my $i = 0 ; $i < @servers ; $i++ ) { | ||||
243 | eval { | ||||
244 | $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 ); | ||||
245 | if ($QParser) { | ||||
246 | $query =~ s/=/:/g; | ||||
247 | $QParser->parse( $query ); | ||||
248 | $query = $QParser->target_syntax($servers[$i]); | ||||
249 | $zoom_queries[$i] = new ZOOM::Query::PQF( $query, $zconns[$i]); | ||||
250 | } else { | ||||
251 | $query =~ s/:/=/g; | ||||
252 | $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]); | ||||
253 | } | ||||
254 | $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] ); | ||||
255 | |||||
256 | # error handling | ||||
257 | my $error = | ||||
258 | $zconns[$i]->errmsg() . " (" | ||||
259 | . $zconns[$i]->errcode() . ") " | ||||
260 | . $zconns[$i]->addinfo() . " " | ||||
261 | . $zconns[$i]->diagset(); | ||||
262 | |||||
263 | return ( $error, undef, undef ) if $zconns[$i]->errcode(); | ||||
264 | }; | ||||
265 | if ($@) { | ||||
266 | |||||
267 | # caught a ZOOM::Exception | ||||
268 | my $error = | ||||
269 | $@->message() . " (" | ||||
270 | . $@->code() . ") " | ||||
271 | . $@->addinfo() . " " | ||||
272 | . $@->diagset(); | ||||
273 | warn $error." for query: $query"; | ||||
274 | return ( $error, undef, undef ); | ||||
275 | } | ||||
276 | } | ||||
277 | |||||
278 | _ZOOM_event_loop( | ||||
279 | \@zconns, | ||||
280 | \@tmpresults, | ||||
281 | sub { | ||||
282 | my ($i, $size) = @_; | ||||
283 | my $first_record = defined($offset) ? $offset + 1 : 1; | ||||
284 | my $hits = $tmpresults[ $i - 1 ]->size(); | ||||
285 | $total_hits += $hits; | ||||
286 | my $last_record = $hits; | ||||
287 | if ( defined $max_results && $offset + $max_results < $hits ) { | ||||
288 | $last_record = $offset + $max_results; | ||||
289 | } | ||||
290 | |||||
291 | for my $j ( $first_record .. $last_record ) { | ||||
292 | my $record = | ||||
293 | $tmpresults[ $i - 1 ]->record( $j - 1 )->raw() | ||||
294 | ; # 0 indexed | ||||
295 | push @{$results}, $record; | ||||
296 | } | ||||
297 | } | ||||
298 | ); | ||||
299 | |||||
300 | foreach my $zoom_query (@zoom_queries) { | ||||
301 | $zoom_query->destroy(); | ||||
302 | } | ||||
303 | |||||
304 | return ( undef, $results, $total_hits ); | ||||
305 | } | ||||
306 | |||||
307 | =head2 getRecords | ||||
308 | |||||
- - | |||||
323 | # spent 190ms (275µs+189) within C4::Search::getRecords which was called:
# once (275µs+189ms) by main::RUNTIME at line 523 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl | ||||
324 | my ( | ||||
325 | 16 | 190µs | $koha_query, $simple_query, $sort_by_ref, $servers_ref, | ||
326 | $results_per_page, $offset, $expanded_facet, $branches, | ||||
327 | $itemtypes, $query_type, $scan, $opac | ||||
328 | ) = @_; | ||||
329 | |||||
330 | my @servers = @$servers_ref; | ||||
331 | my @sort_by = @$sort_by_ref; | ||||
332 | |||||
333 | # Initialize variables for the ZOOM connection and results object | ||||
334 | my $zconn; | ||||
335 | my @zconns; | ||||
336 | my @results; | ||||
337 | my $results_hashref = (); | ||||
338 | |||||
339 | # Initialize variables for the faceted results objects | ||||
340 | my $facets_counter = (); | ||||
341 | my $facets_info = (); | ||||
342 | 1 | 2.16ms | my $facets = getFacets(); # spent 2.16ms making 1 call to C4::Koha::getFacets | ||
343 | 1 | 2.31ms | my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets')||20; # spent 2.31ms making 1 call to C4::Context::preference | ||
344 | |||||
345 | my @facets_loop; # stores the ref to array of hashes for template facets loop | ||||
346 | |||||
347 | ### LOOP THROUGH THE SERVERS | ||||
348 | 8 | 44µs | for ( my $i = 0 ; $i < @servers ; $i++ ) { | ||
349 | 1 | 793µs | $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 ); # spent 793µs making 1 call to C4::Context::Zconn | ||
350 | |||||
351 | # perform the search, create the results objects | ||||
352 | # if this is a local search, use the $koha-query, if it's a federated one, use the federated-query | ||||
353 | 1 | 14µs | my $query_to_use = ($servers[$i] =~ /biblioserver/) ? $koha_query : $simple_query; # spent 14µs making 1 call to C4::Search::CORE:match | ||
354 | |||||
355 | #$query_to_use = $simple_query if $scan; | ||||
356 | warn $simple_query if ( $scan and $DEBUG ); | ||||
357 | |||||
358 | # Check if we've got a query_type defined, if so, use it | ||||
359 | 1 | 2µs | eval { | ||
360 | 1 | 18µ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 { | ||||
374 | 2 | 2.74ms | $results[$i] = $zconns[$i]->search(new ZOOM::Query::CCL2RPN($query_to_use, $zconns[$i])); # spent 2.59ms making 1 call to ZOOM::Query::CCL2RPN::new
# spent 151µs making 1 call to ZOOM::Connection::search | ||
375 | } | ||||
376 | }; | ||||
377 | if ($@) { | ||||
378 | warn "WARNING: query problem with $query_to_use " . $@; | ||||
379 | } | ||||
380 | |||||
381 | # Concatenate the sort_by limits and pass them to the results object | ||||
382 | # Note: sort will override rank | ||||
383 | my $sort_by; | ||||
384 | foreach my $sort (@sort_by) { | ||||
385 | 2 | 8µs | if ( $sort eq "author_az" || $sort eq "author_asc" ) { | ||
386 | $sort_by .= "1=1003 <i "; | ||||
387 | } | ||||
388 | elsif ( $sort eq "author_za" || $sort eq "author_dsc" ) { | ||||
389 | $sort_by .= "1=1003 >i "; | ||||
390 | } | ||||
391 | elsif ( $sort eq "popularity_asc" ) { | ||||
392 | $sort_by .= "1=9003 <i "; | ||||
393 | } | ||||
394 | elsif ( $sort eq "popularity_dsc" ) { | ||||
395 | $sort_by .= "1=9003 >i "; | ||||
396 | } | ||||
397 | elsif ( $sort eq "call_number_asc" ) { | ||||
398 | $sort_by .= "1=8007 <i "; | ||||
399 | } | ||||
400 | elsif ( $sort eq "call_number_dsc" ) { | ||||
401 | $sort_by .= "1=8007 >i "; | ||||
402 | } | ||||
403 | elsif ( $sort eq "pubdate_asc" ) { | ||||
404 | $sort_by .= "1=31 <i "; | ||||
405 | } | ||||
406 | elsif ( $sort eq "pubdate_dsc" ) { | ||||
407 | $sort_by .= "1=31 >i "; | ||||
408 | } | ||||
409 | elsif ( $sort eq "acqdate_asc" ) { | ||||
410 | $sort_by .= "1=32 <i "; | ||||
411 | } | ||||
412 | elsif ( $sort eq "acqdate_dsc" ) { | ||||
413 | $sort_by .= "1=32 >i "; | ||||
414 | } | ||||
415 | elsif ( $sort eq "title_az" || $sort eq "title_asc" ) { | ||||
416 | $sort_by .= "1=4 <i "; | ||||
417 | } | ||||
418 | elsif ( $sort eq "title_za" || $sort eq "title_dsc" ) { | ||||
419 | $sort_by .= "1=4 >i "; | ||||
420 | } | ||||
421 | else { | ||||
422 | warn "Ignoring unrecognized sort '$sort' requested" if $sort_by; | ||||
423 | } | ||||
424 | } | ||||
425 | if ($sort_by && !$scan) { | ||||
426 | if ( $results[$i]->sort( "yaz", $sort_by ) < 0 ) { | ||||
427 | warn "WARNING sort $sort_by failed"; | ||||
428 | } | ||||
429 | } | ||||
430 | } # finished looping through servers | ||||
431 | |||||
432 | # The big moment: asynchronously retrieve results from all servers | ||||
433 | _ZOOM_event_loop( | ||||
434 | \@zconns, | ||||
435 | \@results, | ||||
436 | # spent 113ms (28.8+84.2) within C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] which was called:
# once (28.8ms+84.2ms) by C4::Search::_ZOOM_event_loop at line 2352 | ||||
437 | 10 | 135µs | my ( $i, $size ) = @_; | ||
438 | my $results_hash; | ||||
439 | |||||
440 | # loop through the results | ||||
441 | $results_hash->{'hits'} = $size; | ||||
442 | my $times; | ||||
443 | if ( $offset + $results_per_page <= $size ) { | ||||
444 | $times = $offset + $results_per_page; | ||||
445 | } | ||||
446 | else { | ||||
447 | $times = $size; | ||||
448 | } | ||||
449 | 75 | 51µs | for ( my $j = $offset ; $j < $times ; $j++ ) { | ||
450 | my $records_hash; | ||||
451 | my $record; | ||||
452 | |||||
453 | ## Check if it's an index scan | ||||
454 | 50 | 482µ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 { | ||||
490 | 50 | 36.5ms | $record = $results[ $i - 1 ]->record($j)->raw(); # spent 32.8ms making 25 calls to ZOOM::ResultSet::record, avg 1.31ms/call
# spent 3.69ms making 25 calls to ZOOM::Record::raw, avg 147µs/call | ||
491 | |||||
492 | # warn "RECORD $j:".$record; | ||||
493 | $results_hash->{'RECORDS'}[$j] = $record; | ||||
494 | } | ||||
495 | |||||
496 | } | ||||
497 | $results_hashref->{ $servers[ $i - 1 ] } = $results_hash; | ||||
498 | |||||
499 | # Fill the facets while we're looping, but only for the biblioserver and not for a scan | ||||
500 | 2 | 6µs | 1 | 6µs | if ( !$scan && $servers[ $i - 1 ] =~ /biblioserver/ ) { # spent 6µs making 1 call to C4::Search::CORE:match |
501 | |||||
502 | my $jmax = | ||||
503 | $size > $facets_maxrecs ? $facets_maxrecs : $size; | ||||
504 | for my $facet (@$facets) { | ||||
505 | 525 | 3.57ms | for ( my $j = 0 ; $j < $jmax ; $j++ ) { | ||
506 | 350 | 27.4ms | my $render_record = # spent 14.8ms making 175 calls to ZOOM::Record::render, avg 84µs/call
# spent 12.6ms making 175 calls to ZOOM::ResultSet::record, avg 72µs/call | ||
507 | $results[ $i - 1 ]->record($j)->render(); | ||||
508 | my @used_datas = (); | ||||
509 | foreach my $tag ( @{ $facet->{tags} } ) { | ||||
510 | |||||
511 | # avoid first line | ||||
512 | 1650 | 10.6ms | my $tag_num = substr( $tag, 0, 3 ); | ||
513 | my $letters = substr( $tag, 3 ); | ||||
514 | my $field_pattern = | ||||
515 | '\n' . $tag_num . ' ([^z][^\n]+)'; | ||||
516 | $field_pattern = '\n' . $tag_num . ' ([^\n]+)' | ||||
517 | if ( int($tag_num) < 10 ); | ||||
518 | 550 | 5.50ms | my @field_tokens = # spent 3.33ms making 275 calls to C4::Search::CORE:regcomp, avg 12µs/call
# spent 2.17ms making 275 calls to C4::Search::CORE:match, avg 8µs/call | ||
519 | ( $render_record =~ /$field_pattern/g ); | ||||
520 | foreach my $field_token (@field_tokens) { | ||||
521 | 1512 | 9.56ms | 252 | 2.48ms | my @subf = ( $field_token =~ # spent 2.48ms making 252 calls to C4::Search::CORE:match, avg 10µs/call |
522 | /\$([a-zA-Z0-9]) ([^\$]+)/g ); | ||||
523 | my @values; | ||||
524 | 1496 | 16.1ms | for ( my $i = 0 ; $i < @subf ; $i += 2 ) { | ||
525 | 1008 | 4.13ms | 2992 | 8.30ms | if ( $letters =~ $subf[$i] ) { # spent 6.63ms making 1496 calls to C4::Search::CORE:regcomp, avg 4µs/call
# spent 1.67ms making 1496 calls to C4::Search::CORE:match, avg 1µs/call |
526 | my $value = $subf[ $i + 1 ]; | ||||
527 | 252 | 566µs | $value =~ s/^ *//; # spent 566µs making 252 calls to C4::Search::CORE:subst, avg 2µs/call | ||
528 | 252 | 1.07ms | $value =~ s/ *$//; # spent 1.07ms making 252 calls to C4::Search::CORE:subst, avg 4µs/call | ||
529 | push @values, $value; | ||||
530 | } | ||||
531 | } | ||||
532 | my $data = join( $facet->{sep}, @values ); | ||||
533 | 432 | 759µs | unless ( $data ~~ @used_datas ) { | ||
534 | $facets_counter->{ $facet->{idx} } | ||||
535 | ->{$data}++; | ||||
536 | push @used_datas, $data; | ||||
537 | } | ||||
538 | } # fields | ||||
539 | } # field codes | ||||
540 | 28 | 390µs | } # records | ||
541 | $facets_info->{ $facet->{idx} }->{label_value} = | ||||
542 | $facet->{label}; | ||||
543 | $facets_info->{ $facet->{idx} }->{expanded} = | ||||
544 | $facet->{expanded}; | ||||
545 | } # facets | ||||
546 | } | ||||
547 | |||||
548 | # warn "connection ", $i-1, ": $size hits"; | ||||
549 | # warn $results[$i-1]->record(0)->render() if $size > 0; | ||||
550 | |||||
551 | # BUILD FACETS | ||||
552 | 1 | 29µs | 1 | 3µs | if ( $servers[ $i - 1 ] =~ /biblioserver/ ) { # spent 3µs making 1 call to C4::Search::CORE:match |
553 | 1 | 15µs | for my $link_value ( # spent 15µs making 1 call to C4::Search::CORE:sort | ||
554 | sort { $facets_counter->{$b} <=> $facets_counter->{$a} } | ||||
555 | keys %$facets_counter | ||||
556 | ) | ||||
557 | { | ||||
558 | 30 | 404µs | my $expandable; | ||
559 | my $number_of_facets; | ||||
560 | my @this_facets_array; | ||||
561 | 5 | 247µs | for my $one_facet ( # spent 247µs making 5 calls to C4::Search::CORE:sort, avg 49µs/call | ||
562 | sort { | ||||
563 | $facets_counter->{$link_value} | ||||
564 | ->{$b} <=> $facets_counter->{$link_value} | ||||
565 | ->{$a} | ||||
566 | } keys %{ $facets_counter->{$link_value} } | ||||
567 | ) | ||||
568 | { | ||||
569 | 306 | 198µs | $number_of_facets++; | ||
570 | 207 | 606µs | if ( ( $number_of_facets < 6 ) | ||
571 | || ( $expanded_facet eq $link_value ) | ||||
572 | || ( $facets_info->{$link_value}->{'expanded'} ) | ||||
573 | ) | ||||
574 | { | ||||
575 | |||||
576 | # Sanitize the link value : parenthesis, question and exclamation mark will cause errors with CCL | ||||
577 | my $facet_link_value = $one_facet; | ||||
578 | 1 | 12µs | 24 | 481µs | $facet_link_value =~ s/[()!?¡¿؟]/ /g; # spent 303µs making 23 calls to C4::Search::CORE:subst, avg 13µs/call
# spent 178µs making 1 call to utf8::SWASHNEW |
579 | |||||
580 | # fix the length that will display in the label, | ||||
581 | my $facet_label_value = $one_facet; | ||||
582 | 23 | 1.81ms | my $facet_max_length = C4::Context->preference( # spent 1.81ms making 23 calls to C4::Context::preference, avg 79µs/call | ||
583 | 'FacetLabelTruncationLength') | ||||
584 | || 20; | ||||
585 | $facet_label_value = | ||||
586 | substr( $one_facet, 0, $facet_max_length ) | ||||
587 | . "..." | ||||
588 | if length($facet_label_value) > | ||||
589 | $facet_max_length; | ||||
590 | |||||
591 | # if it's a branch, label by the name, not the code, | ||||
592 | 23 | 23µs | if ( $link_value =~ /branch/ ) { # spent 23µs making 23 calls to C4::Search::CORE:match, avg 1µs/call | ||
593 | if ( defined $branches | ||||
594 | && ref($branches) eq "HASH" | ||||
595 | && defined $branches->{$one_facet} | ||||
596 | && ref( $branches->{$one_facet} ) eq | ||||
597 | "HASH" ) | ||||
598 | { | ||||
599 | $facet_label_value = | ||||
600 | $branches->{$one_facet} | ||||
601 | ->{'branchname'}; | ||||
602 | } | ||||
603 | else { | ||||
604 | $facet_label_value = "*"; | ||||
605 | } | ||||
606 | } | ||||
607 | |||||
608 | # if it's a itemtype, label by the name, not the code, | ||||
609 | 23 | 15µs | if ( $link_value =~ /itype/ ) { # spent 15µs making 23 calls to C4::Search::CORE:match, avg 635ns/call | ||
610 | if ( defined $itemtypes | ||||
611 | && ref($itemtypes) eq "HASH" | ||||
612 | && defined $itemtypes->{$one_facet} | ||||
613 | && ref( $itemtypes->{$one_facet} ) eq | ||||
614 | "HASH" ) | ||||
615 | { | ||||
616 | $facet_label_value = | ||||
617 | $itemtypes->{$one_facet} | ||||
618 | ->{'description'}; | ||||
619 | } | ||||
620 | } | ||||
621 | |||||
622 | # also, if it's a location code, use the name instead of the code | ||||
623 | 23 | 6µs | if ( $link_value =~ /location/ ) { # spent 6µs making 23 calls to C4::Search::CORE:match, avg 265ns/call | ||
624 | $facet_label_value = | ||||
625 | GetKohaAuthorisedValueLib( 'LOC', | ||||
626 | $one_facet, $opac ); | ||||
627 | } | ||||
628 | |||||
629 | # but we're down with the whole label being in the link's title. | ||||
630 | push @this_facets_array, | ||||
631 | { | ||||
632 | facet_count => | ||||
633 | $facets_counter->{$link_value} | ||||
634 | ->{$one_facet}, | ||||
635 | facet_label_value => $facet_label_value, | ||||
636 | facet_title_value => $one_facet, | ||||
637 | facet_link_value => $facet_link_value, | ||||
638 | type_link_value => $link_value, | ||||
639 | } | ||||
640 | if ($facet_label_value); | ||||
641 | } | ||||
642 | } | ||||
643 | |||||
644 | # handle expanded option | ||||
645 | unless ( $facets_info->{$link_value}->{'expanded'} ) { | ||||
646 | $expandable = 1 | ||||
647 | if ( ( $number_of_facets > 6 ) | ||||
648 | && ( $expanded_facet ne $link_value ) ); | ||||
649 | } | ||||
650 | 6 | 8µs | push @facets_loop, # spent 4µs making 1 call to C4::Context::preference
# spent 4µs making 5 calls to C4::Search::CORE:match, avg 720ns/call | ||
651 | { | ||||
652 | type_link_value => $link_value, | ||||
653 | type_id => $link_value . "_id", | ||||
654 | "type_label_" | ||||
655 | . $facets_info->{$link_value}->{'label_value'} => | ||||
656 | 1, | ||||
657 | facets => \@this_facets_array, | ||||
658 | expandable => $expandable, | ||||
659 | expand => $link_value, | ||||
660 | } | ||||
661 | unless ( | ||||
662 | ( | ||||
663 | $facets_info->{$link_value}->{'label_value'} =~ | ||||
664 | /Libraries/ | ||||
665 | ) | ||||
666 | and ( C4::Context->preference('singleBranchMode') ) | ||||
667 | ); | ||||
668 | } | ||||
669 | } | ||||
670 | } | ||||
671 | 1 | 181ms | ); # spent 181ms making 1 call to C4::Search::_ZOOM_event_loop | ||
672 | return ( undef, $results_hashref, \@facets_loop ); | ||||
673 | } | ||||
674 | |||||
675 | sub 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 | ||||
749 | sub _remove_stopwords { | ||||
750 | my ( $operand, $index ) = @_; | ||||
751 | my @stopwords_removed; | ||||
752 | |||||
753 | # phrase and exact-qualified indexes shouldn't have stopwords removed | ||||
754 | if ( $index !~ m/phr|ext/ ) { | ||||
755 | |||||
756 | # remove stopwords from operand : parse all stopwords & remove them (case insensitive) | ||||
757 | # we use IsAlpha unicode definition, to deal correctly with diacritics. | ||||
758 | # otherwise, a French word like "leçon" woudl be split into "le" "çon", "le" | ||||
759 | # is a stopword, we'd get "çon" and wouldn't find anything... | ||||
760 | # | ||||
761 | foreach ( keys %{ C4::Context->stopwords } ) { | ||||
762 | next if ( $_ =~ /(and|or|not)/ ); # don't remove operators | ||||
763 | if ( my ($matched) = ($operand =~ | ||||
764 | /([^\X\p{isAlnum}]\Q$_\E[^\X\p{isAlnum}]|[^\X\p{isAlnum}]\Q$_\E$|^\Q$_\E[^\X\p{isAlnum}])/gi)) | ||||
765 | { | ||||
766 | $operand =~ s/\Q$matched\E/ /gi; | ||||
767 | push @stopwords_removed, $_; | ||||
768 | } | ||||
769 | } | ||||
770 | } | ||||
771 | return ( $operand, \@stopwords_removed ); | ||||
772 | } | ||||
773 | |||||
774 | # TRUNCATION | ||||
775 | # spent 30µs (28+2) within C4::Search::_detect_truncation which was called:
# once (28µs+2µs) by C4::Search::buildQuery at line 1418 | ||||
776 | 6 | 19µs | my ( $operand, $index ) = @_; | ||
777 | my ( @nontruncated, @righttruncated, @lefttruncated, @rightlefttruncated, | ||||
778 | @regexpr ); | ||||
779 | 1 | 1µs | $operand =~ s/^ //g; # spent 1µs making 1 call to C4::Search::CORE:subst | ||
780 | my @wordlist = split( /\s/, $operand ); | ||||
781 | foreach my $word (@wordlist) { | ||||
782 | 1 | 12µs | 3 | 2µs | if ( $word =~ s/^\*([^\*]+)\*$/$1/ ) { # spent 2µs making 3 calls to C4::Search::CORE:subst, avg 500ns/call |
783 | push @rightlefttruncated, $word; | ||||
784 | } | ||||
785 | elsif ( $word =~ s/^\*([^\*]+)$/$1/ ) { | ||||
786 | push @lefttruncated, $word; | ||||
787 | } | ||||
788 | elsif ( $word =~ s/^([^\*]+)\*$/$1/ ) { | ||||
789 | push @righttruncated, $word; | ||||
790 | } | ||||
791 | elsif ( index( $word, "*" ) < 0 ) { | ||||
792 | push @nontruncated, $word; | ||||
793 | } | ||||
794 | else { | ||||
795 | push @regexpr, $word; | ||||
796 | } | ||||
797 | } | ||||
798 | return ( | ||||
799 | \@nontruncated, \@righttruncated, \@lefttruncated, | ||||
800 | \@rightlefttruncated, \@regexpr | ||||
801 | ); | ||||
802 | } | ||||
803 | |||||
804 | # STEMMING | ||||
805 | sub _build_stemmed_operand { | ||||
806 | my ($operand,$lang) = @_; | ||||
807 | require Lingua::Stem::Snowball ; | ||||
808 | my $stemmed_operand=q{}; | ||||
809 | |||||
810 | # If operand contains a digit, it is almost certainly an identifier, and should | ||||
811 | # not be stemmed. This is particularly relevant for ISBNs and ISSNs, which | ||||
812 | # can contain the letter "X" - for example, _build_stemmend_operand would reduce | ||||
813 | # "014100018X" to "x ", which for a MARC21 database would bring up irrelevant | ||||
814 | # results (e.g., "23 x 29 cm." from the 300$c). Bug 2098. | ||||
815 | return $operand if $operand =~ /\d/; | ||||
816 | |||||
817 | # FIXME: the locale should be set based on the user's language and/or search choice | ||||
818 | #warn "$lang"; | ||||
819 | # Make sure we only use the first two letters from the language code | ||||
820 | $lang = lc(substr($lang, 0, 2)); | ||||
821 | # The language codes for the two variants of Norwegian will now be "nb" and "nn", | ||||
822 | # none of which Lingua::Stem::Snowball can use, so we need to "translate" them | ||||
823 | if ($lang eq 'nb' || $lang eq 'nn') { | ||||
824 | $lang = 'no'; | ||||
825 | } | ||||
826 | my $stemmer = Lingua::Stem::Snowball->new( lang => $lang, | ||||
827 | encoding => "UTF-8" ); | ||||
828 | |||||
829 | my @words = split( / /, $operand ); | ||||
830 | my @stems = $stemmer->stem(\@words); | ||||
831 | for my $stem (@stems) { | ||||
832 | $stemmed_operand .= "$stem"; | ||||
833 | $stemmed_operand .= "?" | ||||
834 | unless ( $stem =~ /(and$|or$|not$)/ ) || ( length($stem) < 3 ); | ||||
835 | $stemmed_operand .= " "; | ||||
836 | } | ||||
837 | warn "STEMMED OPERAND: $stemmed_operand" if $DEBUG; | ||||
838 | return $stemmed_operand; | ||||
839 | } | ||||
840 | |||||
841 | # FIELD WEIGHTING | ||||
842 | # spent 56µs (41+15) within C4::Search::_build_weighted_query which was called:
# once (41µs+15µs) by C4::Search::buildQuery at line 1468 | ||||
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 | ||||
846 | 8 | 25µs | my ( $operand, $stemmed_operand, $index ) = @_; | ||
847 | 1 | 9µs | my $stemming = C4::Context->preference("QueryStemming") || 0; # spent 9µs making 1 call to C4::Context::preference | ||
848 | 1 | 3µs | my $weight_fields = C4::Context->preference("QueryWeightFields") || 0; # spent 3µs making 1 call to C4::Context::preference | ||
849 | 1 | 3µs | my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0; # spent 3µs making 1 call to C4::Context::preference | ||
850 | |||||
851 | my $weighted_query .= "(rk=("; # Specifies that we're applying rank | ||||
852 | |||||
853 | # Keyword, or, no index specified | ||||
854 | 6 | 6µs | if ( ( $index eq 'kw' ) || ( !$index ) ) { | ||
855 | $weighted_query .= | ||||
856 | "Title-cover,ext,r1=\"$operand\""; # exact title-cover | ||||
857 | $weighted_query .= " or ti,ext,r2=\"$operand\""; # exact title | ||||
858 | $weighted_query .= " or Title-cover,phr,r3=\"$operand\""; # phrase title | ||||
859 | #$weighted_query .= " or any,ext,r4=$operand"; # exact any | ||||
860 | #$weighted_query .=" or kw,wrdl,r5=\"$operand\""; # word list any | ||||
861 | $weighted_query .= " or wrdl,fuzzy,r8=\"$operand\"" | ||||
862 | if $fuzzy_enabled; # add fuzzy, word list | ||||
863 | $weighted_query .= " or wrdl,right-Truncation,r9=\"$stemmed_operand\"" | ||||
864 | if ( $stemming and $stemmed_operand ) | ||||
865 | ; # add stemming, right truncation | ||||
866 | $weighted_query .= " or wrdl,r9=\"$operand\""; | ||||
867 | |||||
868 | # embedded sorting: 0 a-z; 1 z-a | ||||
869 | # $weighted_query .= ") or (sort1,aut=1"; | ||||
870 | } | ||||
871 | |||||
872 | # Barcode searches should skip this process | ||||
873 | elsif ( $index eq 'bc' ) { | ||||
874 | $weighted_query .= "bc=\"$operand\""; | ||||
875 | } | ||||
876 | |||||
877 | # Authority-number searches should skip this process | ||||
878 | elsif ( $index eq 'an' ) { | ||||
879 | $weighted_query .= "an=\"$operand\""; | ||||
880 | } | ||||
881 | |||||
882 | # If the index already has more than one qualifier, wrap the operand | ||||
883 | # in quotes and pass it back (assumption is that the user knows what they | ||||
884 | # are doing and won't appreciate us mucking up their query | ||||
885 | elsif ( $index =~ ',' ) { | ||||
886 | $weighted_query .= " $index=\"$operand\""; | ||||
887 | } | ||||
888 | |||||
889 | #TODO: build better cases based on specific search indexes | ||||
890 | else { | ||||
891 | $weighted_query .= " $index,ext,r1=\"$operand\""; # exact index | ||||
892 | #$weighted_query .= " or (title-sort-az=0 or $index,startswithnt,st-word,r3=$operand #)"; | ||||
893 | $weighted_query .= " or $index,phr,r3=\"$operand\""; # phrase index | ||||
894 | $weighted_query .= " or $index,wrdl,r6=\"$operand\""; # word list index | ||||
895 | $weighted_query .= " or $index,wrdl,fuzzy,r8=\"$operand\"" | ||||
896 | if $fuzzy_enabled; # add fuzzy, word list | ||||
897 | $weighted_query .= " or $index,wrdl,rt,r9=\"$stemmed_operand\"" | ||||
898 | if ( $stemming and $stemmed_operand ); # add stemming, right truncation | ||||
899 | } | ||||
900 | |||||
901 | $weighted_query .= "))"; # close rank specification | ||||
902 | return $weighted_query; | ||||
903 | } | ||||
904 | |||||
905 | =head2 getIndexes | ||||
906 | |||||
- - | |||||
911 | # spent 74µs within C4::Search::getIndexes which was called:
# once (74µs+0s) by C4::Search::buildQuery at line 1282 | ||||
912 | 2 | 77µs | my @indexes = ( | ||
913 | # biblio indexes | ||||
914 | 'ab', | ||||
915 | 'Abstract', | ||||
916 | 'acqdate', | ||||
917 | 'allrecords', | ||||
918 | 'an', | ||||
919 | 'Any', | ||||
920 | 'at', | ||||
921 | 'au', | ||||
922 | 'aub', | ||||
923 | 'aud', | ||||
924 | 'audience', | ||||
925 | 'auo', | ||||
926 | 'aut', | ||||
927 | 'Author', | ||||
928 | 'Author-in-order ', | ||||
929 | 'Author-personal-bibliography', | ||||
930 | 'Authority-Number', | ||||
931 | 'authtype', | ||||
932 | 'bc', | ||||
933 | 'Bib-level', | ||||
934 | 'biblionumber', | ||||
935 | 'bio', | ||||
936 | 'biography', | ||||
937 | 'callnum', | ||||
938 | 'cfn', | ||||
939 | 'Chronological-subdivision', | ||||
940 | 'cn-bib-source', | ||||
941 | 'cn-bib-sort', | ||||
942 | 'cn-class', | ||||
943 | 'cn-item', | ||||
944 | 'cn-prefix', | ||||
945 | 'cn-suffix', | ||||
946 | 'cpn', | ||||
947 | 'Code-institution', | ||||
948 | 'Conference-name', | ||||
949 | 'Conference-name-heading', | ||||
950 | 'Conference-name-see', | ||||
951 | 'Conference-name-seealso', | ||||
952 | 'Content-type', | ||||
953 | 'Control-number', | ||||
954 | 'copydate', | ||||
955 | 'Corporate-name', | ||||
956 | 'Corporate-name-heading', | ||||
957 | 'Corporate-name-see', | ||||
958 | 'Corporate-name-seealso', | ||||
959 | 'ctype', | ||||
960 | 'date-entered-on-file', | ||||
961 | 'Date-of-acquisition', | ||||
962 | 'Date-of-publication', | ||||
963 | 'Dewey-classification', | ||||
964 | 'EAN', | ||||
965 | 'extent', | ||||
966 | 'fic', | ||||
967 | 'fiction', | ||||
968 | 'Form-subdivision', | ||||
969 | 'format', | ||||
970 | 'Geographic-subdivision', | ||||
971 | 'he', | ||||
972 | 'Heading', | ||||
973 | 'Heading-use-main-or-added-entry', | ||||
974 | 'Heading-use-series-added-entry ', | ||||
975 | 'Heading-use-subject-added-entry', | ||||
976 | 'Host-item', | ||||
977 | 'id-other', | ||||
978 | 'Illustration-code', | ||||
979 | 'ISBN', | ||||
980 | 'isbn', | ||||
981 | 'ISSN', | ||||
982 | 'issn', | ||||
983 | 'itemtype', | ||||
984 | 'kw', | ||||
985 | 'Koha-Auth-Number', | ||||
986 | 'l-format', | ||||
987 | 'language', | ||||
988 | 'lc-card', | ||||
989 | 'LC-card-number', | ||||
990 | 'lcn', | ||||
991 | 'llength', | ||||
992 | 'ln', | ||||
993 | 'Local-classification', | ||||
994 | 'Local-number', | ||||
995 | 'Match-heading', | ||||
996 | 'Match-heading-see-from', | ||||
997 | 'Material-type', | ||||
998 | 'mc-itemtype', | ||||
999 | 'mc-rtype', | ||||
1000 | 'mus', | ||||
1001 | 'name', | ||||
1002 | 'Music-number', | ||||
1003 | 'Name-geographic', | ||||
1004 | 'Name-geographic-heading', | ||||
1005 | 'Name-geographic-see', | ||||
1006 | 'Name-geographic-seealso', | ||||
1007 | 'nb', | ||||
1008 | 'Note', | ||||
1009 | 'notes', | ||||
1010 | 'ns', | ||||
1011 | 'nt', | ||||
1012 | 'pb', | ||||
1013 | 'Personal-name', | ||||
1014 | 'Personal-name-heading', | ||||
1015 | 'Personal-name-see', | ||||
1016 | 'Personal-name-seealso', | ||||
1017 | 'pl', | ||||
1018 | 'Place-publication', | ||||
1019 | 'pn', | ||||
1020 | 'popularity', | ||||
1021 | 'pubdate', | ||||
1022 | 'Publisher', | ||||
1023 | 'Record-control-number', | ||||
1024 | 'rcn', | ||||
1025 | 'Record-type', | ||||
1026 | 'rtype', | ||||
1027 | 'se', | ||||
1028 | 'See', | ||||
1029 | 'See-also', | ||||
1030 | 'sn', | ||||
1031 | 'Stock-number', | ||||
1032 | 'su', | ||||
1033 | 'Subject', | ||||
1034 | 'Subject-heading-thesaurus', | ||||
1035 | 'Subject-name-personal', | ||||
1036 | 'Subject-subdivision', | ||||
1037 | 'Summary', | ||||
1038 | 'Suppress', | ||||
1039 | 'su-geo', | ||||
1040 | 'su-na', | ||||
1041 | 'su-to', | ||||
1042 | 'su-ut', | ||||
1043 | 'ut', | ||||
1044 | 'UPC', | ||||
1045 | 'Term-genre-form', | ||||
1046 | 'Term-genre-form-heading', | ||||
1047 | 'Term-genre-form-see', | ||||
1048 | 'Term-genre-form-seealso', | ||||
1049 | 'ti', | ||||
1050 | 'Title', | ||||
1051 | 'Title-cover', | ||||
1052 | 'Title-series', | ||||
1053 | 'Title-host', | ||||
1054 | 'Title-uniform', | ||||
1055 | 'Title-uniform-heading', | ||||
1056 | 'Title-uniform-see', | ||||
1057 | 'Title-uniform-seealso', | ||||
1058 | 'totalissues', | ||||
1059 | 'yr', | ||||
1060 | |||||
1061 | # items indexes | ||||
1062 | 'acqsource', | ||||
1063 | 'barcode', | ||||
1064 | 'bc', | ||||
1065 | 'branch', | ||||
1066 | 'ccode', | ||||
1067 | 'classification-source', | ||||
1068 | 'cn-sort', | ||||
1069 | 'coded-location-qualifier', | ||||
1070 | 'copynumber', | ||||
1071 | 'damaged', | ||||
1072 | 'datelastborrowed', | ||||
1073 | 'datelastseen', | ||||
1074 | 'holdingbranch', | ||||
1075 | 'homebranch', | ||||
1076 | 'issues', | ||||
1077 | 'item', | ||||
1078 | 'itemnumber', | ||||
1079 | 'itype', | ||||
1080 | 'Local-classification', | ||||
1081 | 'location', | ||||
1082 | 'lost', | ||||
1083 | 'materials-specified', | ||||
1084 | 'mc-ccode', | ||||
1085 | 'mc-itype', | ||||
1086 | 'mc-loc', | ||||
1087 | 'notforloan', | ||||
1088 | 'onloan', | ||||
1089 | 'price', | ||||
1090 | 'renewals', | ||||
1091 | 'replacementprice', | ||||
1092 | 'replacementpricedate', | ||||
1093 | 'reserves', | ||||
1094 | 'restricted', | ||||
1095 | 'stack', | ||||
1096 | 'stocknumber', | ||||
1097 | 'inv', | ||||
1098 | 'uri', | ||||
1099 | 'withdrawn', | ||||
1100 | |||||
1101 | # subject related | ||||
1102 | ); | ||||
1103 | |||||
1104 | return \@indexes; | ||||
1105 | } | ||||
1106 | |||||
1107 | =head2 _handle_exploding_index | ||||
1108 | |||||
- - | |||||
1117 | sub _handle_exploding_index { | ||||
1118 | my ($QParser, $filter, $params, $negate, $server) = @_; | ||||
1119 | my $index = $filter; | ||||
1120 | my $term = join(' ', @$params); | ||||
1121 | |||||
1122 | return unless ($index =~ m/(su-br|su-na|su-rl)/ && $term); | ||||
1123 | |||||
1124 | my $marcflavour = C4::Context->preference('marcflavour'); | ||||
1125 | |||||
1126 | my $codesubfield = $marcflavour eq 'UNIMARC' ? '5' : 'w'; | ||||
1127 | my $wantedcodes = ''; | ||||
1128 | my @subqueries = ( "\@attr 1=Subject \@attr 4=1 \"$term\""); | ||||
1129 | my ($error, $results, $total_hits) = SimpleSearch( "he:$term", undef, undef, [ "authorityserver" ] ); | ||||
1130 | foreach my $auth (@$results) { | ||||
1131 | my $record = MARC::Record->new_from_usmarc($auth); | ||||
1132 | my @references = $record->field('5..'); | ||||
1133 | if (@references) { | ||||
1134 | if ($index eq 'su-br') { | ||||
1135 | $wantedcodes = 'g'; | ||||
1136 | } elsif ($index eq 'su-na') { | ||||
1137 | $wantedcodes = 'h'; | ||||
1138 | } elsif ($index eq 'su-rl') { | ||||
1139 | $wantedcodes = ''; | ||||
1140 | } | ||||
1141 | foreach my $reference (@references) { | ||||
1142 | my $codes = $reference->subfield($codesubfield); | ||||
1143 | push @subqueries, '@attr 1=Subject @attr 4=1 "' . $reference->as_string('abcdefghijlmnopqrstuvxyz') . '"' if (($codes && $codes eq $wantedcodes) || !$wantedcodes); | ||||
1144 | } | ||||
1145 | } | ||||
1146 | } | ||||
1147 | my $query = ' @or ' x (scalar(@subqueries) - 1) . join(' ', @subqueries); | ||||
1148 | return $query; | ||||
1149 | } | ||||
1150 | |||||
1151 | =head2 parseQuery | ||||
1152 | |||||
- - | |||||
1163 | # spent 31.6ms (5.02+26.6) within C4::Search::parseQuery which was called:
# once (5.02ms+26.6ms) by C4::Search::buildQuery at line 1253 | ||||
1164 | 16 | 55µs | my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_; | ||
1165 | |||||
1166 | my @operators = $operators ? @$operators : (); | ||||
1167 | my @indexes = $indexes ? @$indexes : (); | ||||
1168 | my @operands = $operands ? @$operands : (); | ||||
1169 | my @limits = $limits ? @$limits : (); | ||||
1170 | my @sort_by = $sort_by ? @$sort_by : (); | ||||
1171 | |||||
1172 | my $query = $operands[0]; | ||||
1173 | my $index; | ||||
1174 | my $term; | ||||
1175 | my $query_desc; | ||||
1176 | |||||
1177 | my $QParser; | ||||
1178 | 2 | 2.18ms | $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') || $query =~ s/^qp=//); # spent 2.18ms making 1 call to C4::Context::preference
# spent 2µs making 1 call to C4::Search::CORE:subst | ||
1179 | 2 | 2µs | undef $QParser if ($query =~ m/^(ccl=|pqf=|cql=)/ || grep (/\w,\w|\w=\w/, @operands, @indexes) ); # spent 2µs making 2 calls to C4::Search::CORE:match, avg 900ns/call | ||
1180 | undef $QParser if (scalar @limits > 0); | ||||
1181 | |||||
1182 | 4 | 275µs | if ($QParser) | ||
1183 | { | ||||
1184 | $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate'); | ||||
1185 | $query = ''; | ||||
1186 | for ( my $ii = 0 ; $ii <= @operands ; $ii++ ) { | ||||
1187 | next unless $operands[$ii]; | ||||
1188 | $query .= $operators[ $ii - 1 ] eq 'or' ? ' || ' : ' && ' | ||||
1189 | if ($query); | ||||
1190 | if ( $indexes[$ii] =~ m/su-/ ) { | ||||
1191 | $query .= $indexes[$ii] . '(' . $operands[$ii] . ')'; | ||||
1192 | } | ||||
1193 | else { | ||||
1194 | $query .= | ||||
1195 | ( $indexes[$ii] ? "$indexes[$ii]:" : '' ) . $operands[$ii]; | ||||
1196 | } | ||||
1197 | } | ||||
1198 | foreach my $limit (@limits) { | ||||
1199 | } | ||||
1200 | if ( scalar(@sort_by) > 0 ) { | ||||
1201 | my $modifier_re = | ||||
1202 | '#(' . join( '|', @{ $QParser->modifiers } ) . ')'; | ||||
1203 | $query =~ s/$modifier_re//g; | ||||
1204 | foreach my $modifier (@sort_by) { | ||||
1205 | $query .= " #$modifier"; | ||||
1206 | } | ||||
1207 | } | ||||
1208 | |||||
1209 | $query_desc = $query; | ||||
1210 | $query_desc =~ s/\s+/ /g; | ||||
1211 | if ( C4::Context->preference("QueryWeightFields") ) { | ||||
1212 | } | ||||
1213 | $QParser->add_bib1_filter_map( 'su-br' => 'biblioserver' => | ||||
1214 | { 'target_syntax_callback' => \&_handle_exploding_index } ); | ||||
1215 | $QParser->add_bib1_filter_map( 'su-na' => 'biblioserver' => | ||||
1216 | { 'target_syntax_callback' => \&_handle_exploding_index } ); | ||||
1217 | $QParser->add_bib1_filter_map( 'su-rl' => 'biblioserver' => | ||||
1218 | { 'target_syntax_callback' => \&_handle_exploding_index } ); | ||||
1219 | $QParser->parse($query); | ||||
1220 | $operands[0] = "pqf=" . $QParser->target_syntax('biblioserver'); | ||||
1221 | } | ||||
1222 | else { | ||||
1223 | require Koha::QueryParser::Driver::PQF; | ||||
1224 | 1 | 18µs | my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')'; # spent 18µs making 1 call to OpenILS::QueryParser::modifiers | ||
1225 | 2 | 21µs | s/$modifier_re//g for @operands; # spent 20µs making 1 call to C4::Search::CORE:regcomp
# spent 2µs making 1 call to C4::Search::CORE:subst | ||
1226 | } | ||||
1227 | |||||
1228 | return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc); | ||||
1229 | } | ||||
1230 | |||||
1231 | =head2 buildQuery | ||||
1232 | |||||
- - | |||||
1247 | # spent 45.8ms (287µs+45.5) within C4::Search::buildQuery which was called:
# once (287µs+45.5ms) by main::RUNTIME at line 453 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl | ||||
1248 | 44 | 129µs | my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_; | ||
1249 | |||||
1250 | warn "---------\nEnter buildQuery\n---------" if $DEBUG; | ||||
1251 | |||||
1252 | my $query_desc; | ||||
1253 | 1 | 31.6ms | ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang); # spent 31.6ms making 1 call to C4::Search::parseQuery | ||
1254 | |||||
1255 | # dereference | ||||
1256 | my @operators = $operators ? @$operators : (); | ||||
1257 | my @indexes = $indexes ? @$indexes : (); | ||||
1258 | my @operands = $operands ? @$operands : (); | ||||
1259 | my @limits = $limits ? @$limits : (); | ||||
1260 | my @sort_by = $sort_by ? @$sort_by : (); | ||||
1261 | |||||
1262 | 1 | 4.25ms | my $stemming = C4::Context->preference("QueryStemming") || 0; # spent 4.25ms making 1 call to C4::Context::preference | ||
1263 | 1 | 2.16ms | my $auto_truncation = C4::Context->preference("QueryAutoTruncate") || 0; # spent 2.16ms making 1 call to C4::Context::preference | ||
1264 | 1 | 2.53ms | my $weight_fields = C4::Context->preference("QueryWeightFields") || 0; # spent 2.53ms making 1 call to C4::Context::preference | ||
1265 | 1 | 2.11ms | my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0; # spent 2.11ms making 1 call to C4::Context::preference | ||
1266 | 1 | 2.63ms | my $remove_stopwords = C4::Context->preference("QueryRemoveStopwords") || 0; # spent 2.63ms making 1 call to C4::Context::preference | ||
1267 | |||||
1268 | my $query = $operands[0]; | ||||
1269 | my $simple_query = $operands[0]; | ||||
1270 | |||||
1271 | # initialize the variables we're passing back | ||||
1272 | my $query_cgi; | ||||
1273 | my $query_type; | ||||
1274 | |||||
1275 | my $limit; | ||||
1276 | my $limit_cgi; | ||||
1277 | my $limit_desc; | ||||
1278 | |||||
1279 | my $stopwords_removed; # flag to determine if stopwords have been removed | ||||
1280 | |||||
1281 | my $cclq = 0; | ||||
1282 | 1 | 74µs | my $cclindexes = getIndexes(); # spent 74µs making 1 call to C4::Search::getIndexes | ||
1283 | 2 | 6µs | 1 | 2µs | if ( $query !~ /\s*ccl=/ ) { # spent 2µs making 1 call to C4::Search::CORE:match |
1284 | 1 | 400ns | while ( !$cclq && $query =~ /(?:^|\W)([\w-]+)(,[\w-]+)*[:=]/g ) { # spent 400ns making 1 call to C4::Search::CORE:match | ||
1285 | my $dx = lc($1); | ||||
1286 | $cclq = grep { lc($_) eq $dx } @$cclindexes; | ||||
1287 | } | ||||
1288 | $query = "ccl=$query" if $cclq; | ||||
1289 | } | ||||
1290 | |||||
1291 | # for handling ccl, cql, pqf queries in diagnostic mode, skip the rest of the steps | ||||
1292 | # DIAGNOSTIC ONLY!! | ||||
1293 | 1 | 400ns | 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 | } | ||||
1303 | 1 | 600ns | if ( $query =~ /^cql=/ ) { # spent 600ns making 1 call to C4::Search::CORE:match | ||
1304 | return ( undef, $', $', "q=cql=".uri_escape($'), $', '', '', '', '', 'cql' ); | ||||
1305 | } | ||||
1306 | 4 | 6µs | 1 | 500ns | if ( $query =~ /^pqf=/ ) { # spent 500ns making 1 call to C4::Search::CORE:match |
1307 | if ($query_desc) { | ||||
1308 | $query_cgi = "q=".uri_escape($query_desc); | ||||
1309 | } else { | ||||
1310 | $query_desc = $'; | ||||
1311 | $query_cgi = "q=pqf=".uri_escape($'); | ||||
1312 | } | ||||
1313 | return ( undef, $', $', $query_cgi, $query_desc, '', '', '', '', 'pqf' ); | ||||
1314 | } | ||||
1315 | |||||
1316 | # pass nested queries directly | ||||
1317 | # FIXME: need better handling of some of these variables in this case | ||||
1318 | # Nested queries aren't handled well and this implementation is flawed and causes users to be | ||||
1319 | # unable to search for anything containing () commenting out, will be rewritten for 3.4.0 | ||||
1320 | # if ( $query =~ /(\(|\))/ ) { | ||||
1321 | # return ( | ||||
1322 | # undef, $query, $simple_query, $query_cgi, | ||||
1323 | # $query, $limit, $limit_cgi, $limit_desc, | ||||
1324 | # $stopwords_removed, 'ccl' | ||||
1325 | # ); | ||||
1326 | # } | ||||
1327 | |||||
1328 | # Form-based queries are non-nested and fixed depth, so we can easily modify the incoming | ||||
1329 | # query operands and indexes and add stemming, truncation, field weighting, etc. | ||||
1330 | # Once we do so, we'll end up with a value in $query, just like if we had an | ||||
1331 | # incoming $query from the user | ||||
1332 | else { | ||||
1333 | $query = "" | ||||
1334 | ; # clear it out so we can populate properly with field-weighted, stemmed, etc. query | ||||
1335 | my $previous_operand | ||||
1336 | ; # a flag used to keep track if there was a previous query | ||||
1337 | # if there was, we can apply the current operator | ||||
1338 | # for every operand | ||||
1339 | 2 | 2µs | for ( my $i = 0 ; $i <= @operands ; $i++ ) { | ||
1340 | |||||
1341 | # COMBINE OPERANDS, INDEXES AND OPERATORS | ||||
1342 | 26 | 63µs | if ( $operands[$i] ) { | ||
1343 | 1 | 4µs | $operands[$i]=~s/^\s+//; # spent 4µs making 1 call to C4::Search::CORE:subst | ||
1344 | |||||
1345 | # A flag to determine whether or not to add the index to the query | ||||
1346 | my $indexes_set; | ||||
1347 | |||||
1348 | # If the user is sophisticated enough to specify an index, turn off field weighting, stemming, and stopword handling | ||||
1349 | 1 | 4µs | 1 | 500ns | 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 { | ||||
1354 | 1 | 1µs | $operands[$i] =~ s/\?/{?}/g; # need to escape question marks # spent 1µs making 1 call to C4::Search::CORE:subst | ||
1355 | } | ||||
1356 | my $operand = $operands[$i]; | ||||
1357 | my $index = $indexes[$i]; | ||||
1358 | |||||
1359 | # Add index-specific attributes | ||||
1360 | # Date of Publication | ||||
1361 | if ( $index eq 'yr' ) { | ||||
1362 | $index .= ",st-numeric"; | ||||
1363 | $indexes_set++; | ||||
1364 | $stemming = $auto_truncation = $weight_fields = $fuzzy_enabled = $remove_stopwords = 0; | ||||
1365 | } | ||||
1366 | |||||
1367 | # Date of Acquisition | ||||
1368 | elsif ( $index eq 'acqdate' ) { | ||||
1369 | $index .= ",st-date-normalized"; | ||||
1370 | $indexes_set++; | ||||
1371 | $stemming = $auto_truncation = $weight_fields = $fuzzy_enabled = $remove_stopwords = 0; | ||||
1372 | } | ||||
1373 | # ISBN,ISSN,Standard Number, don't need special treatment | ||||
1374 | elsif ( $index eq 'nb' || $index eq 'ns' ) { | ||||
1375 | ( | ||||
1376 | $stemming, $auto_truncation, | ||||
1377 | $weight_fields, $fuzzy_enabled, | ||||
1378 | $remove_stopwords | ||||
1379 | ) = ( 0, 0, 0, 0, 0 ); | ||||
1380 | |||||
1381 | } | ||||
1382 | |||||
1383 | if(not $index){ | ||||
1384 | $index = 'kw'; | ||||
1385 | } | ||||
1386 | |||||
1387 | # Set default structure attribute (word list) | ||||
1388 | my $struct_attr = q{}; | ||||
1389 | 1 | 2µs | unless ( $indexes_set || !$index || $index =~ /(st-|phr|ext|wrdl|nb|ns)/ ) { # spent 2µs making 1 call to C4::Search::CORE:match | ||
1390 | $struct_attr = ",wrdl"; | ||||
1391 | } | ||||
1392 | |||||
1393 | # Some helpful index variants | ||||
1394 | my $index_plus = $index . $struct_attr . ':'; | ||||
1395 | my $index_plus_comma = $index . $struct_attr . ','; | ||||
1396 | |||||
1397 | # Remove Stopwords | ||||
1398 | if ($remove_stopwords) { | ||||
1399 | ( $operand, $stopwords_removed ) = | ||||
1400 | _remove_stopwords( $operand, $index ); | ||||
1401 | warn "OPERAND w/out STOPWORDS: >$operand<" if $DEBUG; | ||||
1402 | warn "REMOVED STOPWORDS: @$stopwords_removed" | ||||
1403 | if ( $stopwords_removed && $DEBUG ); | ||||
1404 | } | ||||
1405 | |||||
1406 | if ($auto_truncation){ | ||||
1407 | unless ( $index =~ /(st-|phr|ext)/ ) { | ||||
1408 | #FIXME only valid with LTR scripts | ||||
1409 | $operand=join(" ",map{ | ||||
1410 | (index($_,"*")>0?"$_":"$_*") | ||||
1411 | }split (/\s+/,$operand)); | ||||
1412 | warn $operand if $DEBUG; | ||||
1413 | } | ||||
1414 | } | ||||
1415 | |||||
1416 | # Detect Truncation | ||||
1417 | my $truncated_operand; | ||||
1418 | 1 | 30µs | my( $nontruncated, $righttruncated, $lefttruncated, # spent 30µs making 1 call to C4::Search::_detect_truncation | ||
1419 | $rightlefttruncated, $regexpr | ||||
1420 | ) = _detect_truncation( $operand, $index ); | ||||
1421 | warn | ||||
1422 | "TRUNCATION: NON:>@$nontruncated< RIGHT:>@$righttruncated< LEFT:>@$lefttruncated< RIGHTLEFT:>@$rightlefttruncated< REGEX:>@$regexpr<" | ||||
1423 | if $DEBUG; | ||||
1424 | |||||
1425 | # Apply Truncation | ||||
1426 | if ( | ||||
1427 | scalar(@$righttruncated) + scalar(@$lefttruncated) + | ||||
1428 | scalar(@$rightlefttruncated) > 0 ) | ||||
1429 | { | ||||
1430 | |||||
1431 | # Don't field weight or add the index to the query, we do it here | ||||
1432 | $indexes_set = 1; | ||||
1433 | undef $weight_fields; | ||||
1434 | my $previous_truncation_operand; | ||||
1435 | if (scalar @$nontruncated) { | ||||
1436 | $truncated_operand .= "$index_plus @$nontruncated "; | ||||
1437 | $previous_truncation_operand = 1; | ||||
1438 | } | ||||
1439 | if (scalar @$righttruncated) { | ||||
1440 | $truncated_operand .= "and " if $previous_truncation_operand; | ||||
1441 | $truncated_operand .= $index_plus_comma . "rtrn:@$righttruncated "; | ||||
1442 | $previous_truncation_operand = 1; | ||||
1443 | } | ||||
1444 | if (scalar @$lefttruncated) { | ||||
1445 | $truncated_operand .= "and " if $previous_truncation_operand; | ||||
1446 | $truncated_operand .= $index_plus_comma . "ltrn:@$lefttruncated "; | ||||
1447 | $previous_truncation_operand = 1; | ||||
1448 | } | ||||
1449 | if (scalar @$rightlefttruncated) { | ||||
1450 | $truncated_operand .= "and " if $previous_truncation_operand; | ||||
1451 | $truncated_operand .= $index_plus_comma . "rltrn:@$rightlefttruncated "; | ||||
1452 | $previous_truncation_operand = 1; | ||||
1453 | } | ||||
1454 | } | ||||
1455 | $operand = $truncated_operand if $truncated_operand; | ||||
1456 | warn "TRUNCATED OPERAND: >$truncated_operand<" if $DEBUG; | ||||
1457 | |||||
1458 | # Handle Stemming | ||||
1459 | my $stemmed_operand; | ||||
1460 | $stemmed_operand = _build_stemmed_operand($operand, $lang) | ||||
1461 | if $stemming; | ||||
1462 | |||||
1463 | warn "STEMMED OPERAND: >$stemmed_operand<" if $DEBUG; | ||||
1464 | |||||
1465 | # Handle Field Weighting | ||||
1466 | my $weighted_operand; | ||||
1467 | 3 | 9µs | if ($weight_fields) { | ||
1468 | 1 | 56µs | $weighted_operand = _build_weighted_query( $operand, $stemmed_operand, $index ); # spent 56µs making 1 call to C4::Search::_build_weighted_query | ||
1469 | $operand = $weighted_operand; | ||||
1470 | $indexes_set = 1; | ||||
1471 | } | ||||
1472 | |||||
1473 | warn "FIELD WEIGHTED OPERAND: >$weighted_operand<" if $DEBUG; | ||||
1474 | |||||
1475 | # If there's a previous operand, we need to add an operator | ||||
1476 | 6 | 12µs | if ($previous_operand) { | ||
1477 | |||||
1478 | # User-specified operator | ||||
1479 | if ( $operators[ $i - 1 ] ) { | ||||
1480 | $query .= " $operators[$i-1] "; | ||||
1481 | $query .= " $index_plus " unless $indexes_set; | ||||
1482 | $query .= " $operand"; | ||||
1483 | $query_cgi .= "&op=".uri_escape($operators[$i-1]); | ||||
1484 | $query_cgi .= "&idx=".uri_escape($index) if $index; | ||||
1485 | $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i]; | ||||
1486 | $query_desc .= | ||||
1487 | " $operators[$i-1] $index_plus $operands[$i]"; | ||||
1488 | } | ||||
1489 | |||||
1490 | # Default operator is and | ||||
1491 | else { | ||||
1492 | $query .= " and "; | ||||
1493 | $query .= "$index_plus " unless $indexes_set; | ||||
1494 | $query .= "$operand"; | ||||
1495 | $query_cgi .= "&op=and&idx=".uri_escape($index) if $index; | ||||
1496 | $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i]; | ||||
1497 | $query_desc .= " and $index_plus $operands[$i]"; | ||||
1498 | } | ||||
1499 | } | ||||
1500 | |||||
1501 | # There isn't a pervious operand, don't need an operator | ||||
1502 | else { | ||||
1503 | |||||
1504 | # Field-weighted queries already have indexes set | ||||
1505 | $query .= " $index_plus " unless $indexes_set; | ||||
1506 | $query .= $operand; | ||||
1507 | $query_desc .= " $index_plus $operands[$i]"; | ||||
1508 | 1 | 37µs | $query_cgi .= "&idx=".uri_escape($index) if $index; # spent 37µs making 1 call to URI::Escape::uri_escape | ||
1509 | 1 | 8µs | $query_cgi .= "&q=".uri_escape($operands[$i]) if $operands[$i]; # spent 8µs making 1 call to URI::Escape::uri_escape | ||
1510 | $previous_operand = 1; | ||||
1511 | } | ||||
1512 | } #/if $operands | ||||
1513 | } # /for | ||||
1514 | } | ||||
1515 | warn "QUERY BEFORE LIMITS: >$query<" if $DEBUG; | ||||
1516 | |||||
1517 | # add limits | ||||
1518 | my %group_OR_limits; | ||||
1519 | my $availability_limit; | ||||
1520 | foreach my $this_limit (@limits) { | ||||
1521 | next unless $this_limit; | ||||
1522 | if ( $this_limit =~ /available/ ) { | ||||
1523 | # | ||||
1524 | ## 'available' is defined as (items.onloan is NULL) and (items.itemlost = 0) | ||||
1525 | ## In English: | ||||
1526 | ## all records not indexed in the onloan register (zebra) and all records with a value of lost equal to 0 | ||||
1527 | $availability_limit .= | ||||
1528 | "( ( allrecords,AlwaysMatches='' not onloan,AlwaysMatches='') and (lost,st-numeric=0) )"; #or ( allrecords,AlwaysMatches='' not lost,AlwaysMatches='')) )"; | ||||
1529 | $limit_cgi .= "&limit=available"; | ||||
1530 | $limit_desc .= ""; | ||||
1531 | } | ||||
1532 | |||||
1533 | # group_OR_limits, prefixed by mc- | ||||
1534 | # OR every member of the group | ||||
1535 | elsif ( $this_limit =~ /mc/ ) { | ||||
1536 | my ($k,$v) = split(/:/, $this_limit,2); | ||||
1537 | if ( $k !~ /mc-i(tem)?type/ ) { | ||||
1538 | # in case the mc-ccode value has complicating chars like ()'s inside it we wrap in quotes | ||||
1539 | $this_limit =~ tr/"//d; | ||||
1540 | $this_limit = $k.":\"".$v."\""; | ||||
1541 | } | ||||
1542 | |||||
1543 | $group_OR_limits{$k} .= " or " if $group_OR_limits{$k}; | ||||
1544 | $limit_desc .= " or " if $group_OR_limits{$k}; | ||||
1545 | $group_OR_limits{$k} .= "$this_limit"; | ||||
1546 | $limit_cgi .= "&limit=$this_limit"; | ||||
1547 | $limit_desc .= " $this_limit"; | ||||
1548 | } | ||||
1549 | |||||
1550 | # Regular old limits | ||||
1551 | else { | ||||
1552 | $limit .= " and " if $limit || $query; | ||||
1553 | $limit .= "$this_limit"; | ||||
1554 | $limit_cgi .= "&limit=$this_limit"; | ||||
1555 | if ($this_limit =~ /^branch:(.+)/) { | ||||
1556 | my $branchcode = $1; | ||||
1557 | my $branchname = GetBranchName($branchcode); | ||||
1558 | if (defined $branchname) { | ||||
1559 | $limit_desc .= " branch:$branchname"; | ||||
1560 | } else { | ||||
1561 | $limit_desc .= " $this_limit"; | ||||
1562 | } | ||||
1563 | } else { | ||||
1564 | $limit_desc .= " $this_limit"; | ||||
1565 | } | ||||
1566 | } | ||||
1567 | } | ||||
1568 | foreach my $k (keys (%group_OR_limits)) { | ||||
1569 | $limit .= " and " if ( $query || $limit ); | ||||
1570 | $limit .= "($group_OR_limits{$k})"; | ||||
1571 | } | ||||
1572 | if ($availability_limit) { | ||||
1573 | $limit .= " and " if ( $query || $limit ); | ||||
1574 | $limit .= "($availability_limit)"; | ||||
1575 | } | ||||
1576 | |||||
1577 | # Normalize the query and limit strings | ||||
1578 | # This is flawed , means we can't search anything with : in it | ||||
1579 | # if user wants to do ccl or cql, start the query with that | ||||
1580 | # $query =~ s/:/=/g; | ||||
1581 | 1 | 2µs | $query =~ s/(?<=(ti|au|pb|su|an|kw|mc|nb|ns)):/=/g; # spent 2µs making 1 call to C4::Search::CORE:subst | ||
1582 | 1 | 700ns | $query =~ s/(?<=(wrdl)):/=/g; # spent 700ns making 1 call to C4::Search::CORE:subst | ||
1583 | 1 | 800ns | $query =~ s/(?<=(trn|phr)):/=/g; # spent 800ns making 1 call to C4::Search::CORE:subst | ||
1584 | 1 | 800ns | $limit =~ s/:/=/g; # spent 800ns making 1 call to C4::Search::CORE:subst | ||
1585 | for ( $query, $query_desc, $limit, $limit_desc ) { | ||||
1586 | 16 | 48µs | 4 | 2µs | s/ +/ /g; # remove extra spaces # spent 2µs making 4 calls to C4::Search::CORE:subst, avg 525ns/call |
1587 | 4 | 5µs | s/^ //g; # remove any beginning spaces # spent 5µs making 4 calls to C4::Search::CORE:subst, avg 1µs/call | ||
1588 | 4 | 1µs | s/ $//g; # remove any ending spaces # spent 1µs making 4 calls to C4::Search::CORE:subst, avg 375ns/call | ||
1589 | 4 | 2µs | s/==/=/g; # remove double == from query # spent 2µs making 4 calls to C4::Search::CORE:subst, avg 475ns/call | ||
1590 | } | ||||
1591 | 1 | 2µs | $query_cgi =~ s/^&//; # remove unnecessary & from beginning of the query cgi # spent 2µs making 1 call to C4::Search::CORE:subst | ||
1592 | |||||
1593 | for ($query_cgi,$simple_query) { | ||||
1594 | 2 | 6µs | 2 | 900ns | s/"//g; # spent 900ns making 2 calls to C4::Search::CORE:subst, avg 450ns/call |
1595 | } | ||||
1596 | # append the limit to the query | ||||
1597 | $query .= " " . $limit; | ||||
1598 | |||||
1599 | # Warnings if DEBUG | ||||
1600 | if ($DEBUG) { | ||||
1601 | warn "QUERY:" . $query; | ||||
1602 | warn "QUERY CGI:" . $query_cgi; | ||||
1603 | warn "QUERY DESC:" . $query_desc; | ||||
1604 | warn "LIMIT:" . $limit; | ||||
1605 | warn "LIMIT CGI:" . $limit_cgi; | ||||
1606 | warn "LIMIT DESC:" . $limit_desc; | ||||
1607 | warn "---------\nLeave buildQuery\n---------"; | ||||
1608 | } | ||||
1609 | return ( | ||||
1610 | undef, $query, $simple_query, $query_cgi, | ||||
1611 | $query_desc, $limit, $limit_cgi, $limit_desc, | ||||
1612 | $stopwords_removed, $query_type | ||||
1613 | ); | ||||
1614 | } | ||||
1615 | |||||
1616 | =head2 searchResults | ||||
1617 | |||||
- - | |||||
1626 | # IMO this subroutine is pretty messy still -- it's responsible for | ||||
1627 | # building the HTML output for the template | ||||
1628 | # spent 5.46s (25.5ms+5.44) within C4::Search::searchResults which was called:
# once (25.5ms+5.44s) by main::RUNTIME at line 560 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl | ||||
1629 | 29 | 7.73ms | my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults ) = @_; | ||
1630 | 1 | 1.09ms | my $dbh = C4::Context->dbh; # spent 1.09ms making 1 call to C4::Context::dbh | ||
1631 | my @newresults; | ||||
1632 | |||||
1633 | require C4::Items; | ||||
1634 | |||||
1635 | $search_context = 'opac' if !$search_context || $search_context ne 'intranet'; | ||||
1636 | my ($is_opac, $hidelostitems); | ||||
1637 | 2 | 11µs | if ($search_context eq 'opac') { | ||
1638 | 1 | 12µs | $hidelostitems = C4::Context->preference('hidelostitems'); # spent 12µs making 1 call to C4::Context::preference | ||
1639 | $is_opac = 1; | ||||
1640 | } | ||||
1641 | |||||
1642 | #Build branchnames hash | ||||
1643 | #find branchname | ||||
1644 | #get branch information..... | ||||
1645 | my %branches; | ||||
1646 | 1 | 10µs | 2 | 249µs | my $bsth =$dbh->prepare("SELECT branchcode,branchname FROM branches"); # FIXME : use C4::Branch::GetBranches # spent 134µs making 1 call to DBI::db::prepare
# spent 114µs making 1 call to DBD::mysql::db::prepare |
1647 | 1 | 925µs | $bsth->execute(); # spent 925µs making 1 call to DBI::st::execute | ||
1648 | 33 | 667µs | while ( my $bdata = $bsth->fetchrow_hashref ) { # spent 517µs making 11 calls to DBI::st::fetchrow_hashref, avg 47µs/call
# spent 75µs making 11 calls to DBI::common::FETCH, avg 7µs/call
# spent 75µs making 11 calls to DBI::st::fetch, avg 7µs/call | ||
1649 | $branches{ $bdata->{'branchcode'} } = $bdata->{'branchname'}; | ||||
1650 | } | ||||
1651 | # FIXME - We build an authorised values hash here, using the default framework | ||||
1652 | # though it is possible to have different authvals for different fws. | ||||
1653 | |||||
1654 | 4 | 4.16ms | my $shelflocations =GetKohaAuthorisedValues('items.location',''); # spent 4.14ms making 1 call to C4::Koha::GetKohaAuthorisedValues
# spent 10µs making 2 calls to DBI::common::DESTROY, avg 5µs/call
# spent 5µs making 1 call to DBD::_mem::common::DESTROY | ||
1655 | |||||
1656 | # get notforloan authorised value list (see $shelflocations FIXME) | ||||
1657 | 4 | 1.71ms | my $notforloan_authorised_value = GetAuthValCode('items.notforloan',''); # spent 1.67ms making 1 call to C4::Koha::GetAuthValCode
# spent 29µs making 2 calls to DBI::common::DESTROY, avg 14µs/call
# spent 5µs making 1 call to DBD::_mem::common::DESTROY | ||
1658 | |||||
1659 | #Build itemtype hash | ||||
1660 | #find itemtype & itemtype image | ||||
1661 | my %itemtypes; | ||||
1662 | 1 | 38µs | 2 | 176µs | $bsth = # spent 95µs making 1 call to DBI::db::prepare
# spent 80µs making 1 call to DBD::mysql::db::prepare |
1663 | $dbh->prepare( | ||||
1664 | "SELECT itemtype,description,imageurl,summary,notforloan FROM itemtypes" | ||||
1665 | ); | ||||
1666 | 4 | 789µs | $bsth->execute(); # spent 778µs making 1 call to DBI::st::execute
# spent 9µs making 2 calls to DBI::common::DESTROY, avg 5µs/call
# spent 3µs making 1 call to DBD::_mem::common::DESTROY | ||
1667 | 72 | 974µs | while ( my $bdata = $bsth->fetchrow_hashref ) { # spent 708µs making 24 calls to DBI::st::fetchrow_hashref, avg 29µs/call
# spent 139µs making 24 calls to DBI::st::fetch, avg 6µs/call
# spent 127µs making 24 calls to DBI::common::FETCH, avg 5µs/call | ||
1668 | foreach (qw(description imageurl summary notforloan)) { | ||||
1669 | 92 | 231µs | $itemtypes{ $bdata->{'itemtype'} }->{$_} = $bdata->{$_}; | ||
1670 | } | ||||
1671 | } | ||||
1672 | |||||
1673 | #search item field code | ||||
1674 | 1 | 12.8ms | my ($itemtag, undef) = &GetMarcFromKohaField( "items.itemnumber", "" ); # spent 12.8ms making 1 call to C4::Biblio::GetMarcFromKohaField | ||
1675 | |||||
1676 | ## find column names of items related to MARC | ||||
1677 | 1 | 5µs | 2 | 110µs | my $sth2 = $dbh->prepare("SHOW COLUMNS FROM items"); # spent 60µs making 1 call to DBI::db::prepare
# spent 51µs making 1 call to DBD::mysql::db::prepare |
1678 | 1 | 3.75ms | $sth2->execute; # spent 3.75ms making 1 call to DBI::st::execute | ||
1679 | my %subfieldstosearch; | ||||
1680 | 80 | 165µs | 41 | 175µs | while ( ( my $column ) = $sth2->fetchrow ) { # spent 175µs making 41 calls to DBI::st::fetchrow, avg 4µs/call |
1681 | 40 | 371µs | my ( $tagfield, $tagsubfield ) = # spent 371µs making 40 calls to C4::Biblio::GetMarcFromKohaField, avg 9µs/call | ||
1682 | &GetMarcFromKohaField( "items." . $column, "" ); | ||||
1683 | $subfieldstosearch{$column} = $tagsubfield; | ||||
1684 | } | ||||
1685 | |||||
1686 | # handle which records to actually retrieve | ||||
1687 | my $times; | ||||
1688 | if ( $hits && $offset + $results_per_page <= $hits ) { | ||||
1689 | $times = $offset + $results_per_page; | ||||
1690 | } | ||||
1691 | else { | ||||
1692 | $times = $hits; # FIXME: if $hits is undefined, why do we want to equal it? | ||||
1693 | } | ||||
1694 | |||||
1695 | 1 | 8µs | my $marcflavour = C4::Context->preference("marcflavour"); # spent 8µs making 1 call to C4::Context::preference | ||
1696 | # We get the biblionumber position in MARC | ||||
1697 | 1 | 8µs | my ($bibliotag,$bibliosubf)=GetMarcFromKohaField('biblio.biblionumber',''); # spent 8µs making 1 call to C4::Biblio::GetMarcFromKohaField | ||
1698 | |||||
1699 | # loop through all of the records we've retrieved | ||||
1700 | 1900 | 9.68ms | for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) { | ||
1701 | 25 | 121ms | my $marcrecord = MARC::File::USMARC::decode( $marcresults->[$i] ); # spent 121ms making 25 calls to MARC::File::USMARC::decode, avg 4.85ms/call | ||
1702 | 125 | 74.5ms | my $fw = $scan # spent 66.7ms making 25 calls to C4::Biblio::GetFrameworkCode, avg 2.67ms/call
# spent 7.32ms making 25 calls to MARC::Record::subfield, avg 293µs/call
# spent 330µs making 50 calls to DBI::common::DESTROY, avg 7µs/call
# spent 84µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call | ||
1703 | ? undef | ||||
1704 | : $bibliotag < 10 | ||||
1705 | ? GetFrameworkCode($marcrecord->field($bibliotag)->data) | ||||
1706 | : GetFrameworkCode($marcrecord->subfield($bibliotag,$bibliosubf)); | ||||
1707 | 25 | 31.0ms | my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, $fw ); # spent 31.0ms making 25 calls to C4::Biblio::TransformMarcToKoha, avg 1.24ms/call | ||
1708 | 100 | 58.2ms | $oldbiblio->{subtitle} = GetRecordValue('subtitle', $marcrecord, $fw); # spent 57.9ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.32ms/call
# spent 248µs making 50 calls to DBI::common::DESTROY, avg 5µs/call
# spent 57µs making 25 calls to DBD::_mem::common::DESTROY, avg 2µs/call | ||
1709 | 100 | 62.8ms | $oldbiblio->{source_t} = GetRecordValue('source_t', $marcrecord, $fw); # spent 62.4ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.50ms/call
# spent 274µs making 50 calls to DBI::common::DESTROY, avg 5µs/call
# spent 67µs making 25 calls to DBD::_mem::common::DESTROY, avg 3µs/call | ||
1710 | 100 | 54.9ms | $oldbiblio->{source_g} = GetRecordValue('source_g', $marcrecord, $fw); # spent 54.6ms making 25 calls to C4::Biblio::GetRecordValue, avg 2.19ms/call
# spent 215µs making 50 calls to DBI::common::DESTROY, avg 4µs/call
# spent 54µs making 25 calls to DBD::_mem::common::DESTROY, avg 2µs/call | ||
1711 | $oldbiblio->{result_number} = $i + 1; | ||||
1712 | |||||
1713 | # add imageurl to itemtype if there is one | ||||
1714 | 25 | 2.05ms | $oldbiblio->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $oldbiblio->{itemtype} }->{imageurl} ); # spent 2.05ms making 25 calls to C4::Koha::getitemtypeimagelocation, avg 82µs/call | ||
1715 | |||||
1716 | 25 | 139µs | $oldbiblio->{'authorised_value_images'} = ($search_context eq 'opac' && C4::Context->preference('AuthorisedValueImages')) || ($search_context eq 'intranet' && C4::Context->preference('StaffAuthorisedValueImages')) ? C4::Items::get_authorised_value_images( C4::Biblio::get_biblio_authorised_values( $oldbiblio->{'biblionumber'}, $marcrecord ) ) : []; # spent 139µs making 25 calls to C4::Context::preference, avg 6µs/call | ||
1717 | 25 | 9.00ms | $oldbiblio->{normalized_upc} = GetNormalizedUPC( $marcrecord,$marcflavour); # spent 9.00ms making 25 calls to C4::Koha::GetNormalizedUPC, avg 360µs/call | ||
1718 | 25 | 7.68ms | $oldbiblio->{normalized_ean} = GetNormalizedEAN( $marcrecord,$marcflavour); # spent 7.68ms making 25 calls to C4::Koha::GetNormalizedEAN, avg 307µs/call | ||
1719 | 25 | 7.58ms | $oldbiblio->{normalized_oclc} = GetNormalizedOCLCNumber($marcrecord,$marcflavour); # spent 7.58ms making 25 calls to C4::Koha::GetNormalizedOCLCNumber, avg 303µs/call | ||
1720 | 25 | 6.88ms | $oldbiblio->{normalized_isbn} = GetNormalizedISBN(undef,$marcrecord,$marcflavour); # spent 6.88ms making 25 calls to C4::Koha::GetNormalizedISBN, avg 275µs/call | ||
1721 | $oldbiblio->{content_identifier_exists} = 1 if ($oldbiblio->{normalized_isbn} or $oldbiblio->{normalized_oclc} or $oldbiblio->{normalized_ean} or $oldbiblio->{normalized_upc}); | ||||
1722 | |||||
1723 | # edition information, if any | ||||
1724 | $oldbiblio->{edition} = $oldbiblio->{editionstatement}; | ||||
1725 | $oldbiblio->{description} = $itemtypes{ $oldbiblio->{itemtype} }->{description}; | ||||
1726 | # Build summary if there is one (the summary is defined in the itemtypes table) | ||||
1727 | # FIXME: is this used anywhere, I think it can be commented out? -- JF | ||||
1728 | if ( $itemtypes{ $oldbiblio->{itemtype} }->{summary} ) { | ||||
1729 | my $summary = $itemtypes{ $oldbiblio->{itemtype} }->{summary}; | ||||
1730 | my @fields = $marcrecord->fields(); | ||||
1731 | |||||
1732 | my $newsummary; | ||||
1733 | foreach my $line ( "$summary\n" =~ /(.*)\n/g ){ | ||||
1734 | my $tags = {}; | ||||
1735 | foreach my $tag ( $line =~ /\[(\d{3}[\w|\d])\]/ ) { | ||||
1736 | $tag =~ /(.{3})(.)/; | ||||
1737 | if($marcrecord->field($1)){ | ||||
1738 | my @abc = $marcrecord->field($1)->subfield($2); | ||||
1739 | $tags->{$tag} = $#abc + 1 ; | ||||
1740 | } | ||||
1741 | } | ||||
1742 | |||||
1743 | # We catch how many times to repeat this line | ||||
1744 | my $max = 0; | ||||
1745 | foreach my $tag (keys(%$tags)){ | ||||
1746 | $max = $tags->{$tag} if($tags->{$tag} > $max); | ||||
1747 | } | ||||
1748 | |||||
1749 | # we replace, and repeat each line | ||||
1750 | for (my $i = 0 ; $i < $max ; $i++){ | ||||
1751 | my $newline = $line; | ||||
1752 | |||||
1753 | foreach my $tag ( $newline =~ /\[(\d{3}[\w|\d])\]/g ) { | ||||
1754 | $tag =~ /(.{3})(.)/; | ||||
1755 | |||||
1756 | if($marcrecord->field($1)){ | ||||
1757 | my @repl = $marcrecord->field($1)->subfield($2); | ||||
1758 | my $subfieldvalue = $repl[$i]; | ||||
1759 | |||||
1760 | if (! utf8::is_utf8($subfieldvalue)) { | ||||
1761 | utf8::decode($subfieldvalue); | ||||
1762 | } | ||||
1763 | |||||
1764 | $newline =~ s/\[$tag\]/$subfieldvalue/g; | ||||
1765 | } | ||||
1766 | } | ||||
1767 | $newsummary .= "$newline\n"; | ||||
1768 | } | ||||
1769 | } | ||||
1770 | |||||
1771 | $newsummary =~ s/\[(.*?)]//g; | ||||
1772 | $newsummary =~ s/\n/<br\/>/g; | ||||
1773 | $oldbiblio->{summary} = $newsummary; | ||||
1774 | } | ||||
1775 | |||||
1776 | # Pull out the items fields | ||||
1777 | 25 | 7.00ms | my @fields = $marcrecord->field($itemtag); # spent 7.00ms making 25 calls to MARC::Record::field, avg 280µs/call | ||
1778 | 25 | 167µs | my $marcflavor = C4::Context->preference("marcflavour"); # spent 167µs making 25 calls to C4::Context::preference, avg 7µs/call | ||
1779 | # adding linked items that belong to host records | ||||
1780 | my $analyticsfield = '773'; | ||||
1781 | if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') { | ||||
1782 | $analyticsfield = '773'; | ||||
1783 | } elsif ($marcflavor eq 'UNIMARC') { | ||||
1784 | $analyticsfield = '461'; | ||||
1785 | } | ||||
1786 | 25 | 7.15ms | foreach my $hostfield ( $marcrecord->field($analyticsfield)) { # spent 7.15ms making 25 calls to MARC::Record::field, avg 286µs/call | ||
1787 | 27 | 129µs | 9 | 199µs | my $hostbiblionumber = $hostfield->subfield("0"); # spent 199µs making 9 calls to MARC::Field::subfield, avg 22µs/call |
1788 | 9 | 107µs | my $linkeditemnumber = $hostfield->subfield("9"); # spent 107µs making 9 calls to MARC::Field::subfield, avg 12µs/call | ||
1789 | if(!$hostbiblionumber eq undef){ | ||||
1790 | my $hostbiblio = GetMarcBiblio($hostbiblionumber, 1); | ||||
1791 | my ($itemfield, undef) = GetMarcFromKohaField( 'items.itemnumber', GetFrameworkCode($hostbiblionumber) ); | ||||
1792 | if(!$hostbiblio eq undef){ | ||||
1793 | my @hostitems = $hostbiblio->field($itemfield); | ||||
1794 | foreach my $hostitem (@hostitems){ | ||||
1795 | if ($hostitem->subfield("9") eq $linkeditemnumber){ | ||||
1796 | my $linkeditem =$hostitem; | ||||
1797 | # append linked items if they exist | ||||
1798 | if (!$linkeditem eq undef){ | ||||
1799 | push (@fields, $linkeditem);} | ||||
1800 | } | ||||
1801 | } | ||||
1802 | } | ||||
1803 | } | ||||
1804 | } | ||||
1805 | |||||
1806 | # Setting item statuses for display | ||||
1807 | my @available_items_loop; | ||||
1808 | my @onloan_items_loop; | ||||
1809 | my @other_items_loop; | ||||
1810 | |||||
1811 | my $available_items; | ||||
1812 | my $onloan_items; | ||||
1813 | my $other_items; | ||||
1814 | |||||
1815 | my $ordered_count = 0; | ||||
1816 | my $available_count = 0; | ||||
1817 | my $onloan_count = 0; | ||||
1818 | my $longoverdue_count = 0; | ||||
1819 | my $other_count = 0; | ||||
1820 | my $wthdrawn_count = 0; | ||||
1821 | my $itemlost_count = 0; | ||||
1822 | my $hideatopac_count = 0; | ||||
1823 | my $itembinding_count = 0; | ||||
1824 | my $itemdamaged_count = 0; | ||||
1825 | my $item_in_transit_count = 0; | ||||
1826 | my $can_place_holds = 0; | ||||
1827 | my $item_onhold_count = 0; | ||||
1828 | my $items_count = scalar(@fields); | ||||
1829 | 25 | 2.68ms | my $maxitems_pref = C4::Context->preference('maxItemsinSearchResults'); # spent 2.68ms making 25 calls to C4::Context::preference, avg 107µs/call | ||
1830 | my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1; | ||||
1831 | my @hiddenitems; # hidden itemnumbers based on OpacHiddenItems syspref | ||||
1832 | |||||
1833 | # loop through every item | ||||
1834 | foreach my $field (@fields) { | ||||
1835 | 320 | 2.65ms | my $item; | ||
1836 | |||||
1837 | # populate the items hash | ||||
1838 | foreach my $code ( keys %subfieldstosearch ) { | ||||
1839 | 1280 | 6.64ms | 1280 | 57.8ms | $item->{$code} = $field->subfield( $subfieldstosearch{$code} ); # spent 57.8ms making 1280 calls to MARC::Field::subfield, avg 45µs/call |
1840 | } | ||||
1841 | $item->{description} = $itemtypes{ $item->{itype} }{description}; | ||||
1842 | |||||
1843 | # OPAC hidden items | ||||
1844 | 96 | 304µs | if ($is_opac) { | ||
1845 | # hidden because lost | ||||
1846 | if ($hidelostitems && $item->{itemlost}) { | ||||
1847 | $hideatopac_count++; | ||||
1848 | next; | ||||
1849 | } | ||||
1850 | # hidden based on OpacHiddenItems syspref | ||||
1851 | 32 | 125ms | my @hi = C4::Items::GetHiddenItemnumbers($item); # spent 125ms making 32 calls to C4::Items::GetHiddenItemnumbers, avg 3.90ms/call | ||
1852 | if (scalar @hi) { | ||||
1853 | push @hiddenitems, @hi; | ||||
1854 | $hideatopac_count++; | ||||
1855 | next; | ||||
1856 | } | ||||
1857 | } | ||||
1858 | |||||
1859 | 32 | 1.85ms | my $hbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'homebranch' : 'holdingbranch'; # spent 1.85ms making 32 calls to C4::Context::preference, avg 58µs/call | ||
1860 | 32 | 119µs | my $otherbranch = C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' ? 'holdingbranch' : 'homebranch'; # spent 119µs making 32 calls to C4::Context::preference, avg 4µs/call | ||
1861 | |||||
1862 | # set item's branch name, use HomeOrHoldingBranch syspref first, fall back to the other one | ||||
1863 | if ($item->{$hbranch}) { | ||||
1864 | $item->{'branchname'} = $branches{$item->{$hbranch}}; | ||||
1865 | } | ||||
1866 | elsif ($item->{$otherbranch}) { # Last resort | ||||
1867 | $item->{'branchname'} = $branches{$item->{$otherbranch}}; | ||||
1868 | } | ||||
1869 | |||||
1870 | my $prefix = $item->{$hbranch} . '--' . $item->{location} . $item->{itype} . $item->{itemcallnumber}; | ||||
1871 | # For each grouping of items (onloan, available, unavailable), we build a key to store relevant info about that item | ||||
1872 | 32 | 302µs | my $userenv = C4::Context->userenv; # spent 302µs making 32 calls to C4::Context::userenv, avg 9µs/call | ||
1873 | 192 | 712µs | if ( $item->{onloan} && !(C4::Members::GetHideLostItemsPreference($userenv->{'number'}) && $item->{itemlost}) ) { | ||
1874 | $onloan_count++; | ||||
1875 | my $key = $prefix . $item->{onloan} . $item->{barcode}; | ||||
1876 | $onloan_items->{$key}->{due_date} = format_date($item->{onloan}); | ||||
1877 | $onloan_items->{$key}->{count}++ if $item->{$hbranch}; | ||||
1878 | $onloan_items->{$key}->{branchname} = $item->{branchname}; | ||||
1879 | $onloan_items->{$key}->{location} = $shelflocations->{ $item->{location} }; | ||||
1880 | $onloan_items->{$key}->{itemcallnumber} = $item->{itemcallnumber}; | ||||
1881 | $onloan_items->{$key}->{description} = $item->{description}; | ||||
1882 | $onloan_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} ); | ||||
1883 | # if something's checked out and lost, mark it as 'long overdue' | ||||
1884 | if ( $item->{itemlost} ) { | ||||
1885 | $onloan_items->{$prefix}->{longoverdue}++; | ||||
1886 | $longoverdue_count++; | ||||
1887 | } else { # can place holds as long as item isn't lost | ||||
1888 | $can_place_holds = 1; | ||||
1889 | } | ||||
1890 | } | ||||
1891 | |||||
1892 | # items not on loan, but still unavailable ( lost, withdrawn, damaged ) | ||||
1893 | else { | ||||
1894 | |||||
1895 | # item is on order | ||||
1896 | if ( $item->{notforloan} < 0 ) { | ||||
1897 | $ordered_count++; | ||||
1898 | } | ||||
1899 | |||||
1900 | # is item in transit? | ||||
1901 | my $transfertwhen = ''; | ||||
1902 | my ($transfertfrom, $transfertto); | ||||
1903 | |||||
1904 | # is item on the reserve shelf? | ||||
1905 | my $reservestatus = ''; | ||||
1906 | |||||
1907 | 64 | 649µ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 | # | ||||
1925 | 128 | 76.5ms | ($transfertwhen, $transfertfrom, $transfertto) = C4::Circulation::GetTransfers($item->{itemnumber}); # spent 76.1ms making 32 calls to C4::Circulation::GetTransfers, avg 2.38ms/call
# spent 320µs making 64 calls to DBI::common::DESTROY, avg 5µs/call
# spent 90µs making 32 calls to DBD::_mem::common::DESTROY, avg 3µs/call | ||
1926 | 128 | 118ms | $reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber}, $oldbiblio->{biblionumber} ); # spent 117ms making 32 calls to C4::Reserves::GetReserveStatus, avg 3.67ms/call
# spent 377µs making 64 calls to DBI::common::DESTROY, avg 6µs/call
# spent 70µs making 32 calls to DBD::_mem::common::DESTROY, avg 2µs/call | ||
1927 | } | ||||
1928 | |||||
1929 | # item is withdrawn, lost, damaged, not for loan, reserved or in transit | ||||
1930 | 192 | 1.03ms | if ( $item->{wthdrawn} | ||
1931 | || $item->{itemlost} | ||||
1932 | || $item->{damaged} | ||||
1933 | || $item->{notforloan} | ||||
1934 | || $reservestatus eq 'Waiting' | ||||
1935 | || ($transfertwhen ne '')) | ||||
1936 | { | ||||
1937 | $wthdrawn_count++ if $item->{wthdrawn}; | ||||
1938 | $itemlost_count++ if $item->{itemlost}; | ||||
1939 | $itemdamaged_count++ if $item->{damaged}; | ||||
1940 | $item_in_transit_count++ if $transfertwhen ne ''; | ||||
1941 | $item_onhold_count++ if $reservestatus eq 'Waiting'; | ||||
1942 | $item->{status} = $item->{wthdrawn} . "-" . $item->{itemlost} . "-" . $item->{damaged} . "-" . $item->{notforloan}; | ||||
1943 | |||||
1944 | # can place hold on item ? | ||||
1945 | if ( !$item->{itemlost} ) { | ||||
1946 | if ( !$item->{wthdrawn} ){ | ||||
1947 | if ( $item->{damaged} ){ | ||||
1948 | if ( C4::Context->preference('AllowHoldsOnDamagedItems') ){ | ||||
1949 | # can place a hold on a damaged item if AllowHoldsOnDamagedItems is true | ||||
1950 | if ( ( !$item->{notforloan} || $item->{notforloan} < 0 ) ){ | ||||
1951 | # item is either for loan or has notforloan < 0 | ||||
1952 | $can_place_holds = 1; | ||||
1953 | } | ||||
1954 | } | ||||
1955 | } elsif ( $item->{notforloan} < 0 ) { | ||||
1956 | # item is not damaged and notforloan is < 0 | ||||
1957 | $can_place_holds = 1; | ||||
1958 | } | ||||
1959 | } | ||||
1960 | } | ||||
1961 | |||||
1962 | $other_count++; | ||||
1963 | |||||
1964 | my $key = $prefix . $item->{status}; | ||||
1965 | foreach (qw(wthdrawn itemlost damaged branchname itemcallnumber)) { | ||||
1966 | $other_items->{$key}->{$_} = $item->{$_}; | ||||
1967 | } | ||||
1968 | $other_items->{$key}->{intransit} = ( $transfertwhen ne '' ) ? 1 : 0; | ||||
1969 | $other_items->{$key}->{onhold} = ($reservestatus) ? 1 : 0; | ||||
1970 | $other_items->{$key}->{notforloan} = GetAuthorisedValueDesc('','',$item->{notforloan},'','',$notforloan_authorised_value) if $notforloan_authorised_value and $item->{notforloan}; | ||||
1971 | $other_items->{$key}->{count}++ if $item->{$hbranch}; | ||||
1972 | $other_items->{$key}->{location} = $shelflocations->{ $item->{location} }; | ||||
1973 | $other_items->{$key}->{description} = $item->{description}; | ||||
1974 | $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} ); | ||||
1975 | } | ||||
1976 | # item is available | ||||
1977 | else { | ||||
1978 | $can_place_holds = 1; | ||||
1979 | $available_count++; | ||||
1980 | $available_items->{$prefix}->{count}++ if $item->{$hbranch}; | ||||
1981 | foreach (qw(branchname itemcallnumber description)) { | ||||
1982 | 96 | 326µs | $available_items->{$prefix}->{$_} = $item->{$_}; | ||
1983 | } | ||||
1984 | $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} }; | ||||
1985 | 32 | 2.79ms | $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} ); # spent 2.79ms making 32 calls to C4::Koha::getitemtypeimagelocation, avg 87µs/call | ||
1986 | } | ||||
1987 | } | ||||
1988 | } # notforloan, item level and biblioitem level | ||||
1989 | |||||
1990 | # if all items are hidden, do not show the record | ||||
1991 | if ($items_count > 0 && $hideatopac_count == $items_count) { | ||||
1992 | next; | ||||
1993 | } | ||||
1994 | |||||
1995 | my ( $availableitemscount, $onloanitemscount, $otheritemscount ); | ||||
1996 | 25 | 168µs | for my $key ( sort keys %$onloan_items ) { # spent 168µs making 25 calls to C4::Search::CORE:sort, avg 7µs/call | ||
1997 | (++$onloanitemscount > $maxitems) and last; | ||||
1998 | push @onloan_items_loop, $onloan_items->{$key}; | ||||
1999 | } | ||||
2000 | 25 | 34µs | for my $key ( sort keys %$other_items ) { # spent 34µs making 25 calls to C4::Search::CORE:sort, avg 1µs/call | ||
2001 | (++$otheritemscount > $maxitems) and last; | ||||
2002 | push @other_items_loop, $other_items->{$key}; | ||||
2003 | } | ||||
2004 | 25 | 37µs | for my $key ( sort keys %$available_items ) { # spent 37µs making 25 calls to C4::Search::CORE:sort, avg 1µs/call | ||
2005 | 59 | 118µs | (++$availableitemscount > $maxitems) and last; | ||
2006 | push @available_items_loop, $available_items->{$key} | ||||
2007 | } | ||||
2008 | |||||
2009 | # XSLT processing of some stuff | ||||
2010 | 3 | 2.24ms | 2 | 242µs | # spent 133µs (24+109) within C4::Search::BEGIN@2010 which was called:
# once (24µs+109µs) by main::BEGIN@48 at line 2010 # spent 133µs making 1 call to C4::Search::BEGIN@2010
# spent 109µs making 1 call to Exporter::import |
2011 | 25 | 114ms | SetUTF8Flag($marcrecord); # spent 114ms making 25 calls to C4::Charset::SetUTF8Flag, avg 4.56ms/call | ||
2012 | warn $marcrecord->as_formatted if $DEBUG; | ||||
2013 | my $interface = $search_context eq 'opac' ? 'OPAC' : ''; | ||||
2014 | 100 | 4.46s | if (!$scan && C4::Context->preference($interface . "XSLTResultsDisplay")) { # spent 4.46s making 25 calls to C4::XSLT::XSLTParse4Display, avg 178ms/call
# spent 1.40ms making 50 calls to XML::LibXML::Node::DESTROY, avg 28µs/call
# spent 356µs making 25 calls to C4::Context::preference, avg 14µs/call | ||
2015 | $oldbiblio->{XSLTResultsRecord} = XSLTParse4Display($oldbiblio->{biblionumber}, $marcrecord, $interface."XSLTResultsDisplay", 1, \@hiddenitems); | ||||
2016 | # the last parameter tells Koha to clean up the problematic ampersand entities that Zebra outputs | ||||
2017 | } | ||||
2018 | |||||
2019 | # if biblio level itypes are used and itemtype is notforloan, it can't be reserved either | ||||
2020 | 25 | 226µs | if (!C4::Context->preference("item-level_itypes")) { # spent 226µs making 25 calls to C4::Context::preference, avg 9µs/call | ||
2021 | if ($itemtypes{ $oldbiblio->{itemtype} }->{notforloan}) { | ||||
2022 | $can_place_holds = 0; | ||||
2023 | } | ||||
2024 | } | ||||
2025 | $oldbiblio->{norequests} = 1 unless $can_place_holds; | ||||
2026 | $oldbiblio->{itemsplural} = 1 if $items_count > 1; | ||||
2027 | $oldbiblio->{items_count} = $items_count; | ||||
2028 | $oldbiblio->{available_items_loop} = \@available_items_loop; | ||||
2029 | $oldbiblio->{onloan_items_loop} = \@onloan_items_loop; | ||||
2030 | $oldbiblio->{other_items_loop} = \@other_items_loop; | ||||
2031 | $oldbiblio->{availablecount} = $available_count; | ||||
2032 | $oldbiblio->{availableplural} = 1 if $available_count > 1; | ||||
2033 | $oldbiblio->{onloancount} = $onloan_count; | ||||
2034 | $oldbiblio->{onloanplural} = 1 if $onloan_count > 1; | ||||
2035 | $oldbiblio->{othercount} = $other_count; | ||||
2036 | $oldbiblio->{otherplural} = 1 if $other_count > 1; | ||||
2037 | $oldbiblio->{wthdrawncount} = $wthdrawn_count; | ||||
2038 | $oldbiblio->{itemlostcount} = $itemlost_count; | ||||
2039 | $oldbiblio->{damagedcount} = $itemdamaged_count; | ||||
2040 | $oldbiblio->{intransitcount} = $item_in_transit_count; | ||||
2041 | $oldbiblio->{onholdcount} = $item_onhold_count; | ||||
2042 | $oldbiblio->{orderedcount} = $ordered_count; | ||||
2043 | |||||
2044 | 25 | 111µs | if (C4::Context->preference("AlternateHoldingsField") && $items_count == 0) { # spent 111µs making 25 calls to C4::Context::preference, avg 4µs/call | ||
2045 | my $fieldspec = C4::Context->preference("AlternateHoldingsField"); | ||||
2046 | my $subfields = substr $fieldspec, 3; | ||||
2047 | my $holdingsep = C4::Context->preference("AlternateHoldingsSeparator") || ' '; | ||||
2048 | my @alternateholdingsinfo = (); | ||||
2049 | my @holdingsfields = $marcrecord->field(substr $fieldspec, 0, 3); | ||||
2050 | my $alternateholdingscount = 0; | ||||
2051 | |||||
2052 | for my $field (@holdingsfields) { | ||||
2053 | my %holding = ( holding => '' ); | ||||
2054 | my $havesubfield = 0; | ||||
2055 | for my $subfield ($field->subfields()) { | ||||
2056 | if ((index $subfields, $$subfield[0]) >= 0) { | ||||
2057 | $holding{'holding'} .= $holdingsep if (length $holding{'holding'} > 0); | ||||
2058 | $holding{'holding'} .= $$subfield[1]; | ||||
2059 | $havesubfield++; | ||||
2060 | } | ||||
2061 | } | ||||
2062 | if ($havesubfield) { | ||||
2063 | push(@alternateholdingsinfo, \%holding); | ||||
2064 | $alternateholdingscount++; | ||||
2065 | } | ||||
2066 | } | ||||
2067 | |||||
2068 | $oldbiblio->{'ALTERNATEHOLDINGS'} = \@alternateholdingsinfo; | ||||
2069 | $oldbiblio->{'alternateholdings_count'} = $alternateholdingscount; | ||||
2070 | } | ||||
2071 | |||||
2072 | push( @newresults, $oldbiblio ); | ||||
2073 | } | ||||
2074 | |||||
2075 | return @newresults; | ||||
2076 | } | ||||
2077 | |||||
2078 | =head2 SearchAcquisitions | ||||
2079 | |||||
- - | |||||
2082 | sub 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 | |||||
- - | |||||
2191 | sub 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 | |||||
2200 | sub 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 | |||||
2211 | sub 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 | |||||
- - | |||||
2265 | sub 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 | |||||
- - | |||||
2291 | sub GetDistinctValues { | ||||
2292 | my ($fieldname,$string)=@_; | ||||
2293 | # returns a reference to a hash of references to branches... | ||||
2294 | if ($fieldname=~/\./){ | ||||
2295 | my ($table,$column)=split /\./, $fieldname; | ||||
2296 | my $dbh = C4::Context->dbh; | ||||
2297 | warn "select DISTINCT($column) as value, count(*) as cnt from $table group by lib order by $column " if $DEBUG; | ||||
2298 | my $sth = $dbh->prepare("select DISTINCT($column) as value, count(*) as cnt from $table ".($string?" where $column like \"$string%\"":"")."group by value order by $column "); | ||||
2299 | $sth->execute; | ||||
2300 | my $elements=$sth->fetchall_arrayref({}); | ||||
2301 | return $elements; | ||||
2302 | } | ||||
2303 | else { | ||||
2304 | $string||= qq(""); | ||||
2305 | my @servers=qw<biblioserver authorityserver>; | ||||
2306 | my (@zconns,@results); | ||||
2307 | for ( my $i = 0 ; $i < @servers ; $i++ ) { | ||||
2308 | $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 ); | ||||
2309 | $results[$i] = | ||||
2310 | $zconns[$i]->scan( | ||||
2311 | ZOOM::Query::CCL2RPN->new( qq"$fieldname $string", $zconns[$i]) | ||||
2312 | ); | ||||
2313 | } | ||||
2314 | # The big moment: asynchronously retrieve results from all servers | ||||
2315 | my @elements; | ||||
2316 | _ZOOM_event_loop( | ||||
2317 | \@zconns, | ||||
2318 | \@results, | ||||
2319 | sub { | ||||
2320 | my ( $i, $size ) = @_; | ||||
2321 | for ( my $j = 0 ; $j < $size ; $j++ ) { | ||||
2322 | my %hashscan; | ||||
2323 | @hashscan{qw(value cnt)} = | ||||
2324 | $results[ $i - 1 ]->display_term($j); | ||||
2325 | push @elements, \%hashscan; | ||||
2326 | } | ||||
2327 | } | ||||
2328 | ); | ||||
2329 | return \@elements; | ||||
2330 | } | ||||
2331 | } | ||||
2332 | |||||
2333 | =head2 _ZOOM_event_loop | ||||
2334 | |||||
- - | |||||
2345 | # spent 181ms (196µs+181) within C4::Search::_ZOOM_event_loop which was called:
# once (196µs+181ms) by C4::Search::getRecords at line 671 | ||||
2346 | 3 | 60µs | my ($zconns, $results, $callback) = @_; | ||
2347 | 22 | 82µs | 12 | 67.7ms | while ( ( my $i = ZOOM::event( $zconns ) ) != 0 ) { # spent 67.7ms making 12 calls to ZOOM::event, avg 5.64ms/call |
2348 | 11 | 202µs | my $ev = $zconns->[ $i - 1 ]->last_event(); # spent 202µs making 11 calls to ZOOM::Connection::last_event, avg 18µs/call | ||
2349 | 3 | 17µs | 11 | 61µs | if ( $ev == ZOOM::Event::ZEND ) { # spent 61µs making 11 calls to ZOOM::Event::ZEND, avg 6µs/call |
2350 | next unless $results->[ $i - 1 ]; | ||||
2351 | 1 | 71µs | my $size = $results->[ $i - 1 ]->size(); # spent 71µs making 1 call to ZOOM::ResultSet::size | ||
2352 | 1 | 113ms | if ( $size > 0 ) { # spent 113ms making 1 call to C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] | ||
2353 | $callback->($i, $size); | ||||
2354 | } | ||||
2355 | } | ||||
2356 | } | ||||
2357 | |||||
2358 | foreach my $result (@$results) { | ||||
2359 | 1 | 6µs | 1 | 77µs | $result->destroy(); # spent 77µs making 1 call to ZOOM::ResultSet::destroy |
2360 | } | ||||
2361 | } | ||||
2362 | |||||
2363 | |||||
2364 | 1 | 115µs | # spent 6µs within C4::Search::END which was called:
# once (6µs+0s) by main::RUNTIME at line 0 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl | ||
2365 | |||||
2366 | 1 | 11µs | 1; | ||
2367 | __END__ | ||||
# spent 6.40ms within C4::Search::CORE:match which was called 2109 times, avg 3µs/call:
# 1496 times (1.67ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 525, avg 1µs/call
# 275 times (2.17ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 518, avg 8µs/call
# 252 times (2.48ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 521, avg 10µs/call
# 23 times (23µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 592, avg 1µs/call
# 23 times (15µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 609, avg 635ns/call
# 23 times (6µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 623, avg 265ns/call
# 5 times (4µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 650, avg 720ns/call
# 2 times (2µs+0s) by C4::Search::parseQuery at line 1179, avg 900ns/call
# once (14µs+0s) by C4::Search::getRecords at line 353
# once (6µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 500
# once (3µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 552
# once (2µs+0s) by C4::Search::buildQuery at line 1283
# once (2µs+0s) by C4::Search::buildQuery at line 1389
# once (600ns+0s) by C4::Search::buildQuery at line 1303
# once (500ns+0s) by C4::Search::buildQuery at line 1306
# once (500ns+0s) by C4::Search::buildQuery at line 1349
# once (400ns+0s) by C4::Search::buildQuery at line 1284
# once (400ns+0s) by C4::Search::buildQuery at line 1293 | |||||
# spent 9.98ms within C4::Search::CORE:regcomp which was called 1772 times, avg 6µs/call:
# 1496 times (6.63ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 525, avg 4µs/call
# 275 times (3.33ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 518, avg 12µs/call
# once (20µs+0s) by C4::Search::parseQuery at line 1225 | |||||
# spent 501µs within C4::Search::CORE:sort which was called 81 times, avg 6µs/call:
# 25 times (168µs+0s) by C4::Search::searchResults at line 1996, avg 7µs/call
# 25 times (37µs+0s) by C4::Search::searchResults at line 2004, avg 1µs/call
# 25 times (34µs+0s) by C4::Search::searchResults at line 2000, avg 1µs/call
# 5 times (247µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 561, avg 49µs/call
# once (15µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 553 | |||||
# spent 1.96ms (1.78+178µs) within C4::Search::CORE:subst which was called 558 times, avg 4µs/call:
# 252 times (1.07ms+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 528, avg 4µs/call
# 252 times (566µs+0s) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 527, avg 2µs/call
# 23 times (125µs+178µs) by C4::Search::__ANON__[/usr/share/koha/lib/C4/Search.pm:670] at line 578, avg 13µs/call
# 4 times (5µs+0s) by C4::Search::buildQuery at line 1587, avg 1µs/call
# 4 times (2µs+0s) by C4::Search::buildQuery at line 1586, avg 525ns/call
# 4 times (2µs+0s) by C4::Search::buildQuery at line 1589, avg 475ns/call
# 4 times (1µs+0s) by C4::Search::buildQuery at line 1588, avg 375ns/call
# 3 times (2µs+0s) by C4::Search::_detect_truncation at line 782, avg 500ns/call
# 2 times (900ns+0s) by C4::Search::buildQuery at line 1594, avg 450ns/call
# once (4µs+0s) by C4::Search::buildQuery at line 1343
# once (2µs+0s) by C4::Search::parseQuery at line 1178
# once (2µs+0s) by C4::Search::buildQuery at line 1591
# once (2µs+0s) by C4::Search::buildQuery at line 1581
# once (2µs+0s) by C4::Search::parseQuery at line 1225
# once (1µs+0s) by C4::Search::buildQuery at line 1354
# once (1µs+0s) by C4::Search::_detect_truncation at line 779
# once (800ns+0s) by C4::Search::buildQuery at line 1584
# once (800ns+0s) by C4::Search::buildQuery at line 1583
# once (700ns+0s) by C4::Search::buildQuery at line 1582 |