| 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 | C4::Search::__ANON__[:670] |
| 1 | 1 | 1 | 25.5ms | 5.46s | C4::Search::searchResults |
| 1772 | 3 | 1 | 9.98ms | 9.98ms | C4::Search::CORE:regcomp (opcode) |
| 2109 | 18 | 1 | 6.40ms | 6.40ms | C4::Search::CORE:match (opcode) |
| 1 | 1 | 1 | 5.02ms | 31.6ms | C4::Search::parseQuery |
| 1 | 1 | 1 | 4.70ms | 8.93ms | C4::Search::BEGIN@24 |
| 1 | 1 | 1 | 3.39ms | 19.3ms | C4::Search::BEGIN@29 |
| 1 | 1 | 1 | 2.89ms | 10.1ms | C4::Search::BEGIN@36 |
| 558 | 19 | 1 | 1.78ms | 1.96ms | C4::Search::CORE:subst (opcode) |
| 1 | 1 | 1 | 1.51ms | 1.77ms | C4::Search::BEGIN@25 |
| 1 | 1 | 1 | 929µs | 5.78ms | C4::Search::BEGIN@34 |
| 81 | 5 | 1 | 501µs | 501µs | C4::Search::CORE:sort (opcode) |
| 1 | 1 | 1 | 287µs | 45.8ms | C4::Search::buildQuery |
| 1 | 1 | 1 | 275µs | 190ms | C4::Search::getRecords |
| 1 | 1 | 1 | 196µs | 181ms | C4::Search::_ZOOM_event_loop |
| 1 | 1 | 1 | 74µs | 74µs | C4::Search::getIndexes |
| 1 | 1 | 1 | 41µs | 56µs | C4::Search::_build_weighted_query |
| 1 | 1 | 1 | 36µs | 129µs | C4::Search::BEGIN@26 |
| 1 | 1 | 1 | 33µs | 42µs | C4::Search::BEGIN@18 |
| 1 | 1 | 1 | 29µs | 824µs | C4::Search::BEGIN@23 |
| 1 | 1 | 1 | 29µs | 1.36ms | C4::Search::BEGIN@22 |
| 1 | 1 | 1 | 28µs | 30µs | C4::Search::_detect_truncation |
| 1 | 1 | 1 | 28µs | 100µs | C4::Search::BEGIN@28 |
| 1 | 1 | 1 | 26µs | 93µs | C4::Search::BEGIN@27 |
| 1 | 1 | 1 | 25µs | 314µs | C4::Search::BEGIN@30 |
| 1 | 1 | 1 | 24µs | 133µs | C4::Search::BEGIN@2010 |
| 1 | 1 | 1 | 22µs | 27µs | C4::Search::BEGIN@21 |
| 1 | 1 | 1 | 22µs | 44µs | C4::Search::BEGIN@37 |
| 1 | 1 | 1 | 17µs | 22µs | C4::Search::BEGIN@39 |
| 1 | 1 | 1 | 16µs | 88µs | C4::Search::BEGIN@33 |
| 1 | 1 | 1 | 15µs | 105µs | C4::Search::BEGIN@32 |
| 1 | 1 | 1 | 15µs | 19µs | C4::Search::BEGIN@38 |
| 1 | 1 | 1 | 15µs | 69µs | C4::Search::BEGIN@35 |
| 1 | 1 | 1 | 14µs | 370µs | C4::Search::BEGIN@31 |
| 1 | 1 | 1 | 11µs | 99µs | C4::Search::BEGIN@40 |
| 1 | 1 | 1 | 7µs | 7µs | C4::Search::BEGIN@43 |
| 1 | 1 | 1 | 6µs | 6µs | C4::Search::END |
| 0 | 0 | 0 | 0s | 0s | C4::Search::AddSearchHistory |
| 0 | 0 | 0 | 0s | 0s | C4::Search::FindDuplicate |
| 0 | 0 | 0 | 0s | 0s | C4::Search::GetDistinctValues |
| 0 | 0 | 0 | 0s | 0s | C4::Search::GetSearchHistory |
| 0 | 0 | 0 | 0s | 0s | C4::Search::SearchAcquisitions |
| 0 | 0 | 0 | 0s | 0s | C4::Search::SimpleSearch |
| 0 | 0 | 0 | 0s | 0s | C4::Search::__ANON__[:2327] |
| 0 | 0 | 0 | 0s | 0s | C4::Search::__ANON__[:297] |
| 0 | 0 | 0 | 0s | 0s | C4::Search::_build_stemmed_operand |
| 0 | 0 | 0 | 0s | 0s | C4::Search::_handle_exploding_index |
| 0 | 0 | 0 | 0s | 0s | C4::Search::_remove_stopwords |
| 0 | 0 | 0 | 0s | 0s | C4::Search::enabled_staff_search_views |
| 0 | 0 | 0 | 0s | 0s | C4::Search::pazGetRecords |
| 0 | 0 | 0 | 0s | 0s | C4::Search::z3950_search_args |
| 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 | 1 | 1µs | $VERSION = 3.07.00.049; | ||
| 45 | 1 | 7µs | $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 | 1 | 5µ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 | 1 | 3µs | my @servers = @$servers_ref; | ||
| 331 | 1 | 2µs | my @sort_by = @$sort_by_ref; | ||
| 332 | |||||
| 333 | # Initialize variables for the ZOOM connection and results object | ||||
| 334 | 1 | 500ns | my $zconn; | ||
| 335 | 1 | 400ns | my @zconns; | ||
| 336 | 1 | 300ns | my @results; | ||
| 337 | 1 | 700ns | my $results_hashref = (); | ||
| 338 | |||||
| 339 | # Initialize variables for the faceted results objects | ||||
| 340 | 1 | 400ns | my $facets_counter = (); | ||
| 341 | 1 | 500ns | my $facets_info = (); | ||
| 342 | 1 | 8µs | 1 | 2.16ms | my $facets = getFacets(); # spent 2.16ms making 1 call to C4::Koha::getFacets |
| 343 | 1 | 8µs | 1 | 2.31ms | my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets')||20; # spent 2.31ms making 1 call to C4::Context::preference |
| 344 | |||||
| 345 | 1 | 600ns | my @facets_loop; # stores the ref to array of hashes for template facets loop | ||
| 346 | |||||
| 347 | ### LOOP THROUGH THE SERVERS | ||||
| 348 | 1 | 5µs | for ( my $i = 0 ; $i < @servers ; $i++ ) { | ||
| 349 | 1 | 8µs | 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 | 26µs | 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 | 1 | 1µs | 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 | 2µ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 | 1 | 18µs | 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 | 1 | 400ns | 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 | 1 | 700ns | my $sort_by; | ||
| 384 | 1 | 3µs | foreach my $sort (@sort_by) { | ||
| 385 | 1 | 7µ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 | 1 | 400ns | warn "Ignoring unrecognized sort '$sort' requested" if $sort_by; | ||
| 423 | } | ||||
| 424 | } | ||||
| 425 | 1 | 700ns | if ($sort_by && !$scan) { | ||
| 426 | if ( $results[$i]->sort( "yaz", $sort_by ) < 0 ) { | ||||
| 427 | warn "WARNING sort $sort_by failed"; | ||||
| 428 | } | ||||
| 429 | } | ||||
| 430 | 1 | 800ns | } # 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 | 1 | 2µs | my ( $i, $size ) = @_; | ||
| 438 | 1 | 500ns | my $results_hash; | ||
| 439 | |||||
| 440 | # loop through the results | ||||
| 441 | 1 | 2µs | $results_hash->{'hits'} = $size; | ||
| 442 | 1 | 400ns | my $times; | ||
| 443 | 1 | 5µs | if ( $offset + $results_per_page <= $size ) { | ||
| 444 | $times = $offset + $results_per_page; | ||||
| 445 | } | ||||
| 446 | else { | ||||
| 447 | $times = $size; | ||||
| 448 | } | ||||
| 449 | 1 | 77µs | for ( my $j = $offset ; $j < $times ; $j++ ) { | ||
| 450 | 25 | 9µs | my $records_hash; | ||
| 451 | 25 | 9µs | my $record; | ||
| 452 | |||||
| 453 | ## Check if it's an index scan | ||||
| 454 | 25 | 33µ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 | 25 | 368µs | 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 | 25 | 113µs | $results_hash->{'RECORDS'}[$j] = $record; | ||
| 494 | } | ||||
| 495 | |||||
| 496 | 1 | 700ns | } | ||
| 497 | 1 | 7µs | $results_hashref->{ $servers[ $i - 1 ] } = $results_hash; | ||
| 498 | |||||
| 499 | # Fill the facets while we're looping, but only for the biblioserver and not for a scan | ||||
| 500 | 1 | 19µs | 1 | 6µs | if ( !$scan && $servers[ $i - 1 ] =~ /biblioserver/ ) { # spent 6µs making 1 call to C4::Search::CORE:match |
| 501 | |||||
| 502 | 1 | 4µs | my $jmax = | ||
| 503 | $size > $facets_maxrecs ? $facets_maxrecs : $size; | ||||
| 504 | 1 | 2µs | for my $facet (@$facets) { | ||
| 505 | 7 | 325µs | for ( my $j = 0 ; $j < $jmax ; $j++ ) { | ||
| 506 | 175 | 2.78ms | 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 | 175 | 152µs | my @used_datas = (); | ||
| 509 | 175 | 633µs | foreach my $tag ( @{ $facet->{tags} } ) { | ||
| 510 | |||||
| 511 | # avoid first line | ||||
| 512 | 275 | 385µs | my $tag_num = substr( $tag, 0, 3 ); | ||
| 513 | 275 | 161µs | my $letters = substr( $tag, 3 ); | ||
| 514 | 275 | 280µs | my $field_pattern = | ||
| 515 | '\n' . $tag_num . ' ([^z][^\n]+)'; | ||||
| 516 | 275 | 636µs | $field_pattern = '\n' . $tag_num . ' ([^\n]+)' | ||
| 517 | if ( int($tag_num) < 10 ); | ||||
| 518 | 275 | 8.14ms | 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 | 275 | 1.01ms | foreach my $field_token (@field_tokens) { | ||
| 521 | 252 | 4.27ms | 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 | 252 | 66µs | my @values; | ||
| 524 | 252 | 3.18ms | for ( my $i = 0 ; $i < @subf ; $i += 2 ) { | ||
| 525 | 1496 | 16.1ms | 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 | 252 | 369µs | my $value = $subf[ $i + 1 ]; | ||
| 527 | 252 | 1.35ms | 252 | 566µs | $value =~ s/^ *//; # spent 566µs making 252 calls to C4::Search::CORE:subst, avg 2µs/call |
| 528 | 252 | 1.96ms | 252 | 1.07ms | $value =~ s/ *$//; # spent 1.07ms making 252 calls to C4::Search::CORE:subst, avg 4µs/call |
| 529 | 252 | 449µs | push @values, $value; | ||
| 530 | } | ||||
| 531 | 252 | 96µs | } | ||
| 532 | 252 | 677µs | my $data = join( $facet->{sep}, @values ); | ||
| 533 | 252 | 1.28ms | unless ( $data ~~ @used_datas ) { | ||
| 534 | 216 | 558µs | $facets_counter->{ $facet->{idx} } | ||
| 535 | ->{$data}++; | ||||
| 536 | 216 | 201µs | push @used_datas, $data; | ||
| 537 | } | ||||
| 538 | } # fields | ||||
| 539 | } # field codes | ||||
| 540 | 7 | 4µs | } # records | ||
| 541 | 7 | 35µs | $facets_info->{ $facet->{idx} }->{label_value} = | ||
| 542 | $facet->{label}; | ||||
| 543 | 7 | 26µs | $facets_info->{ $facet->{idx} }->{expanded} = | ||
| 544 | $facet->{expanded}; | ||||
| 545 | } # facets | ||||
| 546 | } | ||||
| 547 | |||||
| 548 | # warn "connection ", $i-1, ": $size hits"; | ||||
| 549 | # warn $results[$i-1]->record(0)->render() if $size > 0; | ||||
| 550 | |||||
| 551 | # BUILD FACETS | ||||
| 552 | 1 | 22µs | 1 | 3µs | if ( $servers[ $i - 1 ] =~ /biblioserver/ ) { # spent 3µs making 1 call to C4::Search::CORE:match |
| 553 | 1 | 29µs | 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 | 5 | 1µs | my $expandable; | ||
| 559 | 5 | 1µs | my $number_of_facets; | ||
| 560 | 5 | 2µs | my @this_facets_array; | ||
| 561 | 5 | 327µs | 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 | 153 | 33µs | $number_of_facets++; | ||
| 570 | 153 | 165µ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 | 23 | 11µs | my $facet_link_value = $one_facet; | ||
| 578 | 23 | 176µ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 | 23 | 9µs | my $facet_label_value = $one_facet; | ||
| 582 | 23 | 73µs | 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 | 23 | 27µs | $facet_label_value = | ||
| 586 | substr( $one_facet, 0, $facet_max_length ) | ||||
| 587 | . "..." | ||||
| 588 | if length($facet_label_value) > | ||||
| 589 | $facet_max_length; | ||||
| 590 | |||||
| 591 | # if it's a branch, label by the name, not the code, | ||||
| 592 | 23 | 92µs | 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 | 68µs | 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 | 46µs | 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 | 23 | 116µs | push @this_facets_array, | ||
| 631 | { | ||||
| 632 | facet_count => | ||||
| 633 | $facets_counter->{$link_value} | ||||
| 634 | ->{$one_facet}, | ||||
| 635 | facet_label_value => $facet_label_value, | ||||
| 636 | facet_title_value => $one_facet, | ||||
| 637 | facet_link_value => $facet_link_value, | ||||
| 638 | type_link_value => $link_value, | ||||
| 639 | } | ||||
| 640 | if ($facet_label_value); | ||||
| 641 | } | ||||
| 642 | } | ||||
| 643 | |||||
| 644 | # handle expanded option | ||||
| 645 | 5 | 8µs | unless ( $facets_info->{$link_value}->{'expanded'} ) { | ||
| 646 | $expandable = 1 | ||||
| 647 | if ( ( $number_of_facets > 6 ) | ||||
| 648 | && ( $expanded_facet ne $link_value ) ); | ||||
| 649 | } | ||||
| 650 | 5 | 65µs | 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 | 85µs | 1 | 181ms | ); # spent 181ms making 1 call to C4::Search::_ZOOM_event_loop |
| 672 | 1 | 70µs | 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 | 1 | 1µs | my ( $operand, $index ) = @_; | ||
| 777 | 1 | 800ns | my ( @nontruncated, @righttruncated, @lefttruncated, @rightlefttruncated, | ||
| 778 | @regexpr ); | ||||
| 779 | 1 | 5µs | 1 | 1µs | $operand =~ s/^ //g; # spent 1µs making 1 call to C4::Search::CORE:subst |
| 780 | 1 | 4µs | my @wordlist = split( /\s/, $operand ); | ||
| 781 | 1 | 2µs | 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 | 1 | 6µs | \@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 | 1 | 3µs | my ( $operand, $stemmed_operand, $index ) = @_; | ||
| 847 | 1 | 6µs | 1 | 9µs | my $stemming = C4::Context->preference("QueryStemming") || 0; # spent 9µs making 1 call to C4::Context::preference |
| 848 | 1 | 4µs | 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 | 1 | 3µs | my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0; # spent 3µs making 1 call to C4::Context::preference |
| 850 | |||||
| 851 | 1 | 1µs | my $weighted_query .= "(rk=("; # Specifies that we're applying rank | ||
| 852 | |||||
| 853 | # Keyword, or, no index specified | ||||
| 854 | 1 | 2µs | if ( ( $index eq 'kw' ) || ( !$index ) ) { | ||
| 855 | 1 | 1µs | $weighted_query .= | ||
| 856 | "Title-cover,ext,r1=\"$operand\""; # exact title-cover | ||||
| 857 | 1 | 900ns | $weighted_query .= " or ti,ext,r2=\"$operand\""; # exact title | ||
| 858 | 1 | 1µs | $weighted_query .= " or Title-cover,phr,r3=\"$operand\""; # phrase title | ||
| 859 | #$weighted_query .= " or any,ext,r4=$operand"; # exact any | ||||
| 860 | #$weighted_query .=" or kw,wrdl,r5=\"$operand\""; # word list any | ||||
| 861 | 1 | 400ns | $weighted_query .= " or wrdl,fuzzy,r8=\"$operand\"" | ||
| 862 | if $fuzzy_enabled; # add fuzzy, word list | ||||
| 863 | 1 | 500ns | $weighted_query .= " or wrdl,right-Truncation,r9=\"$stemmed_operand\"" | ||
| 864 | if ( $stemming and $stemmed_operand ) | ||||
| 865 | ; # add stemming, right truncation | ||||
| 866 | 1 | 1µs | $weighted_query .= " or wrdl,r9=\"$operand\""; | ||
| 867 | |||||
| 868 | # embedded sorting: 0 a-z; 1 z-a | ||||
| 869 | # $weighted_query .= ") or (sort1,aut=1"; | ||||
| 870 | } | ||||
| 871 | |||||
| 872 | # Barcode searches should skip this process | ||||
| 873 | elsif ( $index eq 'bc' ) { | ||||
| 874 | $weighted_query .= "bc=\"$operand\""; | ||||
| 875 | } | ||||
| 876 | |||||
| 877 | # Authority-number searches should skip this process | ||||
| 878 | elsif ( $index eq 'an' ) { | ||||
| 879 | $weighted_query .= "an=\"$operand\""; | ||||
| 880 | } | ||||
| 881 | |||||
| 882 | # If the index already has more than one qualifier, wrap the operand | ||||
| 883 | # in quotes and pass it back (assumption is that the user knows what they | ||||
| 884 | # are doing and won't appreciate us mucking up their query | ||||
| 885 | elsif ( $index =~ ',' ) { | ||||
| 886 | $weighted_query .= " $index=\"$operand\""; | ||||
| 887 | } | ||||
| 888 | |||||
| 889 | #TODO: build better cases based on specific search indexes | ||||
| 890 | else { | ||||
| 891 | $weighted_query .= " $index,ext,r1=\"$operand\""; # exact index | ||||
| 892 | #$weighted_query .= " or (title-sort-az=0 or $index,startswithnt,st-word,r3=$operand #)"; | ||||
| 893 | $weighted_query .= " or $index,phr,r3=\"$operand\""; # phrase index | ||||
| 894 | $weighted_query .= " or $index,wrdl,r6=\"$operand\""; # word list index | ||||
| 895 | $weighted_query .= " or $index,wrdl,fuzzy,r8=\"$operand\"" | ||||
| 896 | if $fuzzy_enabled; # add fuzzy, word list | ||||
| 897 | $weighted_query .= " or $index,wrdl,rt,r9=\"$stemmed_operand\"" | ||||
| 898 | if ( $stemming and $stemmed_operand ); # add stemming, right truncation | ||||
| 899 | } | ||||
| 900 | |||||
| 901 | 1 | 700ns | $weighted_query .= "))"; # close rank specification | ||
| 902 | 1 | 6µs | return $weighted_query; | ||
| 903 | } | ||||
| 904 | |||||
| 905 | =head2 getIndexes | ||||
| 906 | |||||
| - - | |||||
| 911 | # spent 74µs within C4::Search::getIndexes which was called:
# once (74µs+0s) by C4::Search::buildQuery at line 1282 | ||||
| 912 | 1 | 69µ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 | 1 | 8µs | 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 | 1 | 2µs | my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_; | ||
| 1165 | |||||
| 1166 | 1 | 1µs | my @operators = $operators ? @$operators : (); | ||
| 1167 | 1 | 700ns | my @indexes = $indexes ? @$indexes : (); | ||
| 1168 | 1 | 1µs | my @operands = $operands ? @$operands : (); | ||
| 1169 | 1 | 600ns | my @limits = $limits ? @$limits : (); | ||
| 1170 | 1 | 900ns | my @sort_by = $sort_by ? @$sort_by : (); | ||
| 1171 | |||||
| 1172 | 1 | 500ns | my $query = $operands[0]; | ||
| 1173 | 1 | 300ns | my $index; | ||
| 1174 | 1 | 200ns | my $term; | ||
| 1175 | 1 | 300ns | my $query_desc; | ||
| 1176 | |||||
| 1177 | 1 | 400ns | my $QParser; | ||
| 1178 | 1 | 16µs | 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 | 1 | 16µs | 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 | 1 | 2µs | undef $QParser if (scalar @limits > 0); | ||
| 1181 | |||||
| 1182 | 1 | 2µ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 | 1 | 230µs | require Koha::QueryParser::Driver::PQF; | ||
| 1224 | 1 | 10µs | 1 | 18µs | my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')'; # spent 18µs making 1 call to OpenILS::QueryParser::modifiers |
| 1225 | 2 | 35µs | 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 | 1 | 10µs | return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc); | ||
| 1229 | } | ||||
| 1230 | |||||
| 1231 | =head2 buildQuery | ||||
| 1232 | |||||
| - - | |||||
| 1247 | # spent 45.8ms (287µs+45.5) within C4::Search::buildQuery which was called:
# once (287µs+45.5ms) by main::RUNTIME at line 453 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl | ||||
| 1248 | 1 | 2µs | my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_; | ||
| 1249 | |||||
| 1250 | 1 | 1µs | warn "---------\nEnter buildQuery\n---------" if $DEBUG; | ||
| 1251 | |||||
| 1252 | 1 | 600ns | my $query_desc; | ||
| 1253 | 1 | 7µs | 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 | 1 | 2µs | my @operators = $operators ? @$operators : (); | ||
| 1257 | 1 | 800ns | my @indexes = $indexes ? @$indexes : (); | ||
| 1258 | 1 | 1µs | my @operands = $operands ? @$operands : (); | ||
| 1259 | 1 | 800ns | my @limits = $limits ? @$limits : (); | ||
| 1260 | 1 | 2µs | my @sort_by = $sort_by ? @$sort_by : (); | ||
| 1261 | |||||
| 1262 | 1 | 9µs | 1 | 4.25ms | my $stemming = C4::Context->preference("QueryStemming") || 0; # spent 4.25ms making 1 call to C4::Context::preference |
| 1263 | 1 | 7µs | 1 | 2.16ms | my $auto_truncation = C4::Context->preference("QueryAutoTruncate") || 0; # spent 2.16ms making 1 call to C4::Context::preference |
| 1264 | 1 | 6µs | 1 | 2.53ms | my $weight_fields = C4::Context->preference("QueryWeightFields") || 0; # spent 2.53ms making 1 call to C4::Context::preference |
| 1265 | 1 | 5µs | 1 | 2.11ms | my $fuzzy_enabled = C4::Context->preference("QueryFuzzy") || 0; # spent 2.11ms making 1 call to C4::Context::preference |
| 1266 | 1 | 6µs | 1 | 2.63ms | my $remove_stopwords = C4::Context->preference("QueryRemoveStopwords") || 0; # spent 2.63ms making 1 call to C4::Context::preference |
| 1267 | |||||
| 1268 | 1 | 2µs | my $query = $operands[0]; | ||
| 1269 | 1 | 500ns | my $simple_query = $operands[0]; | ||
| 1270 | |||||
| 1271 | # initialize the variables we're passing back | ||||
| 1272 | 1 | 400ns | my $query_cgi; | ||
| 1273 | 1 | 400ns | my $query_type; | ||
| 1274 | |||||
| 1275 | 1 | 200ns | my $limit; | ||
| 1276 | 1 | 200ns | my $limit_cgi; | ||
| 1277 | 1 | 300ns | my $limit_desc; | ||
| 1278 | |||||
| 1279 | 1 | 400ns | my $stopwords_removed; # flag to determine if stopwords have been removed | ||
| 1280 | |||||
| 1281 | 1 | 300ns | my $cclq = 0; | ||
| 1282 | 1 | 5µs | 1 | 74µs | my $cclindexes = getIndexes(); # spent 74µs making 1 call to C4::Search::getIndexes |
| 1283 | 1 | 8µs | 1 | 2µs | if ( $query !~ /\s*ccl=/ ) { # spent 2µs making 1 call to C4::Search::CORE:match |
| 1284 | 1 | 5µs | 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 | 1 | 600ns | $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 | 3µs | 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 | 4µs | 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 | 1 | 4µ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 | 1 | 1µs | $query = "" | ||
| 1334 | ; # clear it out so we can populate properly with field-weighted, stemmed, etc. query | ||||
| 1335 | 1 | 500ns | 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 | 1 | 4µs | for ( my $i = 0 ; $i <= @operands ; $i++ ) { | ||
| 1340 | |||||
| 1341 | # COMBINE OPERANDS, INDEXES AND OPERATORS | ||||
| 1342 | 2 | 2µs | if ( $operands[$i] ) { | ||
| 1343 | 1 | 8µs | 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 | 1 | 500ns | 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 | 5µ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 | 4µs | 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 | 1 | 700ns | my $operand = $operands[$i]; | ||
| 1357 | 1 | 800ns | my $index = $indexes[$i]; | ||
| 1358 | |||||
| 1359 | # Add index-specific attributes | ||||
| 1360 | # Date of Publication | ||||
| 1361 | 1 | 3µs | if ( $index eq 'yr' ) { | ||
| 1362 | $index .= ",st-numeric"; | ||||
| 1363 | $indexes_set++; | ||||
| 1364 | $stemming = $auto_truncation = $weight_fields = $fuzzy_enabled = $remove_stopwords = 0; | ||||
| 1365 | } | ||||
| 1366 | |||||
| 1367 | # Date of Acquisition | ||||
| 1368 | elsif ( $index eq 'acqdate' ) { | ||||
| 1369 | $index .= ",st-date-normalized"; | ||||
| 1370 | $indexes_set++; | ||||
| 1371 | $stemming = $auto_truncation = $weight_fields = $fuzzy_enabled = $remove_stopwords = 0; | ||||
| 1372 | } | ||||
| 1373 | # ISBN,ISSN,Standard Number, don't need special treatment | ||||
| 1374 | elsif ( $index eq 'nb' || $index eq 'ns' ) { | ||||
| 1375 | ( | ||||
| 1376 | $stemming, $auto_truncation, | ||||
| 1377 | $weight_fields, $fuzzy_enabled, | ||||
| 1378 | $remove_stopwords | ||||
| 1379 | ) = ( 0, 0, 0, 0, 0 ); | ||||
| 1380 | |||||
| 1381 | } | ||||
| 1382 | |||||
| 1383 | 1 | 1µs | if(not $index){ | ||
| 1384 | $index = 'kw'; | ||||
| 1385 | } | ||||
| 1386 | |||||
| 1387 | # Set default structure attribute (word list) | ||||
| 1388 | 1 | 800ns | my $struct_attr = q{}; | ||
| 1389 | 1 | 6µs | 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 | 1 | 2µs | my $index_plus = $index . $struct_attr . ':'; | ||
| 1395 | 1 | 1µs | my $index_plus_comma = $index . $struct_attr . ','; | ||
| 1396 | |||||
| 1397 | # Remove Stopwords | ||||
| 1398 | 1 | 600ns | 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 | 1 | 500ns | 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 | 1 | 600ns | my $truncated_operand; | ||
| 1418 | 1 | 6µs | 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 | 1 | 1µs | warn | ||
| 1422 | "TRUNCATION: NON:>@$nontruncated< RIGHT:>@$righttruncated< LEFT:>@$lefttruncated< RIGHTLEFT:>@$rightlefttruncated< REGEX:>@$regexpr<" | ||||
| 1423 | if $DEBUG; | ||||
| 1424 | |||||
| 1425 | # Apply Truncation | ||||
| 1426 | 1 | 11µs | if ( | ||
| 1427 | scalar(@$righttruncated) + scalar(@$lefttruncated) + | ||||
| 1428 | scalar(@$rightlefttruncated) > 0 ) | ||||
| 1429 | { | ||||
| 1430 | |||||
| 1431 | # Don't field weight or add the index to the query, we do it here | ||||
| 1432 | $indexes_set = 1; | ||||
| 1433 | undef $weight_fields; | ||||
| 1434 | my $previous_truncation_operand; | ||||
| 1435 | if (scalar @$nontruncated) { | ||||
| 1436 | $truncated_operand .= "$index_plus @$nontruncated "; | ||||
| 1437 | $previous_truncation_operand = 1; | ||||
| 1438 | } | ||||
| 1439 | if (scalar @$righttruncated) { | ||||
| 1440 | $truncated_operand .= "and " if $previous_truncation_operand; | ||||
| 1441 | $truncated_operand .= $index_plus_comma . "rtrn:@$righttruncated "; | ||||
| 1442 | $previous_truncation_operand = 1; | ||||
| 1443 | } | ||||
| 1444 | if (scalar @$lefttruncated) { | ||||
| 1445 | $truncated_operand .= "and " if $previous_truncation_operand; | ||||
| 1446 | $truncated_operand .= $index_plus_comma . "ltrn:@$lefttruncated "; | ||||
| 1447 | $previous_truncation_operand = 1; | ||||
| 1448 | } | ||||
| 1449 | if (scalar @$rightlefttruncated) { | ||||
| 1450 | $truncated_operand .= "and " if $previous_truncation_operand; | ||||
| 1451 | $truncated_operand .= $index_plus_comma . "rltrn:@$rightlefttruncated "; | ||||
| 1452 | $previous_truncation_operand = 1; | ||||
| 1453 | } | ||||
| 1454 | } | ||||
| 1455 | 1 | 7µs | $operand = $truncated_operand if $truncated_operand; | ||
| 1456 | 1 | 1µs | warn "TRUNCATED OPERAND: >$truncated_operand<" if $DEBUG; | ||
| 1457 | |||||
| 1458 | # Handle Stemming | ||||
| 1459 | 1 | 800ns | my $stemmed_operand; | ||
| 1460 | 1 | 700ns | $stemmed_operand = _build_stemmed_operand($operand, $lang) | ||
| 1461 | if $stemming; | ||||
| 1462 | |||||
| 1463 | 1 | 400ns | warn "STEMMED OPERAND: >$stemmed_operand<" if $DEBUG; | ||
| 1464 | |||||
| 1465 | # Handle Field Weighting | ||||
| 1466 | 1 | 400ns | my $weighted_operand; | ||
| 1467 | 1 | 1µs | if ($weight_fields) { | ||
| 1468 | 1 | 8µs | 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 | 1 | 700ns | $operand = $weighted_operand; | ||
| 1470 | 1 | 500ns | $indexes_set = 1; | ||
| 1471 | } | ||||
| 1472 | |||||
| 1473 | 1 | 400ns | warn "FIELD WEIGHTED OPERAND: >$weighted_operand<" if $DEBUG; | ||
| 1474 | |||||
| 1475 | # If there's a previous operand, we need to add an operator | ||||
| 1476 | 1 | 2µ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 | 1 | 500ns | $query .= " $index_plus " unless $indexes_set; | ||
| 1506 | 1 | 700ns | $query .= $operand; | ||
| 1507 | 1 | 2µs | $query_desc .= " $index_plus $operands[$i]"; | ||
| 1508 | 1 | 6µs | 1 | 37µs | $query_cgi .= "&idx=".uri_escape($index) if $index; # spent 37µs making 1 call to URI::Escape::uri_escape |
| 1509 | 1 | 3µs | 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 | 1 | 700ns | $previous_operand = 1; | ||
| 1511 | } | ||||
| 1512 | } #/if $operands | ||||
| 1513 | 1 | 700ns | } # /for | ||
| 1514 | } | ||||
| 1515 | 1 | 400ns | warn "QUERY BEFORE LIMITS: >$query<" if $DEBUG; | ||
| 1516 | |||||
| 1517 | # add limits | ||||
| 1518 | 1 | 500ns | my %group_OR_limits; | ||
| 1519 | 1 | 500ns | my $availability_limit; | ||
| 1520 | 1 | 2µs | foreach my $this_limit (@limits) { | ||
| 1521 | next unless $this_limit; | ||||
| 1522 | if ( $this_limit =~ /available/ ) { | ||||
| 1523 | # | ||||
| 1524 | ## 'available' is defined as (items.onloan is NULL) and (items.itemlost = 0) | ||||
| 1525 | ## In English: | ||||
| 1526 | ## all records not indexed in the onloan register (zebra) and all records with a value of lost equal to 0 | ||||
| 1527 | $availability_limit .= | ||||
| 1528 | "( ( allrecords,AlwaysMatches='' not onloan,AlwaysMatches='') and (lost,st-numeric=0) )"; #or ( allrecords,AlwaysMatches='' not lost,AlwaysMatches='')) )"; | ||||
| 1529 | $limit_cgi .= "&limit=available"; | ||||
| 1530 | $limit_desc .= ""; | ||||
| 1531 | } | ||||
| 1532 | |||||
| 1533 | # group_OR_limits, prefixed by mc- | ||||
| 1534 | # OR every member of the group | ||||
| 1535 | elsif ( $this_limit =~ /mc/ ) { | ||||
| 1536 | my ($k,$v) = split(/:/, $this_limit,2); | ||||
| 1537 | if ( $k !~ /mc-i(tem)?type/ ) { | ||||
| 1538 | # in case the mc-ccode value has complicating chars like ()'s inside it we wrap in quotes | ||||
| 1539 | $this_limit =~ tr/"//d; | ||||
| 1540 | $this_limit = $k.":\"".$v."\""; | ||||
| 1541 | } | ||||
| 1542 | |||||
| 1543 | $group_OR_limits{$k} .= " or " if $group_OR_limits{$k}; | ||||
| 1544 | $limit_desc .= " or " if $group_OR_limits{$k}; | ||||
| 1545 | $group_OR_limits{$k} .= "$this_limit"; | ||||
| 1546 | $limit_cgi .= "&limit=$this_limit"; | ||||
| 1547 | $limit_desc .= " $this_limit"; | ||||
| 1548 | } | ||||
| 1549 | |||||
| 1550 | # Regular old limits | ||||
| 1551 | else { | ||||
| 1552 | $limit .= " and " if $limit || $query; | ||||
| 1553 | $limit .= "$this_limit"; | ||||
| 1554 | $limit_cgi .= "&limit=$this_limit"; | ||||
| 1555 | if ($this_limit =~ /^branch:(.+)/) { | ||||
| 1556 | my $branchcode = $1; | ||||
| 1557 | my $branchname = GetBranchName($branchcode); | ||||
| 1558 | if (defined $branchname) { | ||||
| 1559 | $limit_desc .= " branch:$branchname"; | ||||
| 1560 | } else { | ||||
| 1561 | $limit_desc .= " $this_limit"; | ||||
| 1562 | } | ||||
| 1563 | } else { | ||||
| 1564 | $limit_desc .= " $this_limit"; | ||||
| 1565 | } | ||||
| 1566 | } | ||||
| 1567 | } | ||||
| 1568 | 1 | 3µs | foreach my $k (keys (%group_OR_limits)) { | ||
| 1569 | $limit .= " and " if ( $query || $limit ); | ||||
| 1570 | $limit .= "($group_OR_limits{$k})"; | ||||
| 1571 | } | ||||
| 1572 | 1 | 600ns | 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 | 5µs | 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 | 4µs | 1 | 700ns | $query =~ s/(?<=(wrdl)):/=/g; # spent 700ns making 1 call to C4::Search::CORE:subst |
| 1583 | 1 | 4µs | 1 | 800ns | $query =~ s/(?<=(trn|phr)):/=/g; # spent 800ns making 1 call to C4::Search::CORE:subst |
| 1584 | 1 | 4µs | 1 | 800ns | $limit =~ s/:/=/g; # spent 800ns making 1 call to C4::Search::CORE:subst |
| 1585 | 1 | 2µs | for ( $query, $query_desc, $limit, $limit_desc ) { | ||
| 1586 | 4 | 9µ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 | 11µs | 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 | 17µs | 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 | 11µs | 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 | 4µs | 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 | 1 | 1µs | 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 | 1 | 1µs | $query .= " " . $limit; | ||
| 1598 | |||||
| 1599 | # Warnings if DEBUG | ||||
| 1600 | 1 | 500ns | 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 | 1 | 18µs | undef, $query, $simple_query, $query_cgi, | ||
| 1611 | $query_desc, $limit, $limit_cgi, $limit_desc, | ||||
| 1612 | $stopwords_removed, $query_type | ||||
| 1613 | ); | ||||
| 1614 | } | ||||
| 1615 | |||||
| 1616 | =head2 searchResults | ||||
| 1617 | |||||
| - - | |||||
| 1626 | # IMO this subroutine is pretty messy still -- it's responsible for | ||||
| 1627 | # building the HTML output for the template | ||||
| 1628 | # spent 5.46s (25.5ms+5.44) within C4::Search::searchResults which was called:
# once (25.5ms+5.44s) by main::RUNTIME at line 560 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl | ||||
| 1629 | 1 | 3µs | my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults ) = @_; | ||
| 1630 | 1 | 6µs | 1 | 1.09ms | my $dbh = C4::Context->dbh; # spent 1.09ms making 1 call to C4::Context::dbh |
| 1631 | 1 | 400ns | my @newresults; | ||
| 1632 | |||||
| 1633 | 1 | 4µs | require C4::Items; | ||
| 1634 | |||||
| 1635 | 1 | 3µs | $search_context = 'opac' if !$search_context || $search_context ne 'intranet'; | ||
| 1636 | 1 | 1µs | my ($is_opac, $hidelostitems); | ||
| 1637 | 1 | 2µs | if ($search_context eq 'opac') { | ||
| 1638 | 1 | 10µs | 1 | 12µs | $hidelostitems = C4::Context->preference('hidelostitems'); # spent 12µs making 1 call to C4::Context::preference |
| 1639 | 1 | 900ns | $is_opac = 1; | ||
| 1640 | } | ||||
| 1641 | |||||
| 1642 | #Build branchnames hash | ||||
| 1643 | #find branchname | ||||
| 1644 | #get branch information..... | ||||
| 1645 | 1 | 600ns | my %branches; | ||
| 1646 | 1 | 24µ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 | 942µs | 1 | 925µs | $bsth->execute(); # spent 925µs making 1 call to DBI::st::execute |
| 1648 | 1 | 614µs | 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 | 1 | 10µs | 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 | 1 | 9µs | 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 | 1 | 600ns | my %itemtypes; | ||
| 1662 | 1 | 51µ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 | 1 | 791µs | 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 | 1 | 879µs | 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 | 8µs | 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 | 12µ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.76ms | 1 | 3.75ms | $sth2->execute; # spent 3.75ms making 1 call to DBI::st::execute |
| 1679 | 1 | 900ns | my %subfieldstosearch; | ||
| 1680 | 1 | 297µ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 | 107µs | 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 | 40 | 58µs | $subfieldstosearch{$column} = $tagsubfield; | ||
| 1684 | } | ||||
| 1685 | |||||
| 1686 | # handle which records to actually retrieve | ||||
| 1687 | 1 | 400ns | my $times; | ||
| 1688 | 1 | 3µs | if ( $hits && $offset + $results_per_page <= $hits ) { | ||
| 1689 | $times = $offset + $results_per_page; | ||||
| 1690 | } | ||||
| 1691 | else { | ||||
| 1692 | $times = $hits; # FIXME: if $hits is undefined, why do we want to equal it? | ||||
| 1693 | } | ||||
| 1694 | |||||
| 1695 | 1 | 5µs | 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 | 3µs | 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 | 1 | 102µs | for ( my $i = $offset ; $i <= $times - 1 ; $i++ ) { | ||
| 1701 | 25 | 202µs | 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 | 25 | 367µs | 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 | 175µs | 25 | 31.0ms | my $oldbiblio = TransformMarcToKoha( $dbh, $marcrecord, $fw ); # spent 31.0ms making 25 calls to C4::Biblio::TransformMarcToKoha, avg 1.24ms/call |
| 1708 | 25 | 259µs | 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 | 25 | 213µs | 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 | 25 | 225µs | 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 | 25 | 88µs | $oldbiblio->{result_number} = $i + 1; | ||
| 1712 | |||||
| 1713 | # add imageurl to itemtype if there is one | ||||
| 1714 | 25 | 372µs | 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 | 298µs | 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 | 221µs | 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 | 180µs | 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 | 177µs | 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 | 221µs | 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 | 25 | 91µs | $oldbiblio->{content_identifier_exists} = 1 if ($oldbiblio->{normalized_isbn} or $oldbiblio->{normalized_oclc} or $oldbiblio->{normalized_ean} or $oldbiblio->{normalized_upc}); | ||
| 1722 | |||||
| 1723 | # edition information, if any | ||||
| 1724 | 25 | 46µs | $oldbiblio->{edition} = $oldbiblio->{editionstatement}; | ||
| 1725 | 25 | 141µs | $oldbiblio->{description} = $itemtypes{ $oldbiblio->{itemtype} }->{description}; | ||
| 1726 | # Build summary if there is one (the summary is defined in the itemtypes table) | ||||
| 1727 | # FIXME: is this used anywhere, I think it can be commented out? -- JF | ||||
| 1728 | 25 | 57µs | if ( $itemtypes{ $oldbiblio->{itemtype} }->{summary} ) { | ||
| 1729 | my $summary = $itemtypes{ $oldbiblio->{itemtype} }->{summary}; | ||||
| 1730 | my @fields = $marcrecord->fields(); | ||||
| 1731 | |||||
| 1732 | my $newsummary; | ||||
| 1733 | foreach my $line ( "$summary\n" =~ /(.*)\n/g ){ | ||||
| 1734 | my $tags = {}; | ||||
| 1735 | foreach my $tag ( $line =~ /\[(\d{3}[\w|\d])\]/ ) { | ||||
| 1736 | $tag =~ /(.{3})(.)/; | ||||
| 1737 | if($marcrecord->field($1)){ | ||||
| 1738 | my @abc = $marcrecord->field($1)->subfield($2); | ||||
| 1739 | $tags->{$tag} = $#abc + 1 ; | ||||
| 1740 | } | ||||
| 1741 | } | ||||
| 1742 | |||||
| 1743 | # We catch how many times to repeat this line | ||||
| 1744 | my $max = 0; | ||||
| 1745 | foreach my $tag (keys(%$tags)){ | ||||
| 1746 | $max = $tags->{$tag} if($tags->{$tag} > $max); | ||||
| 1747 | } | ||||
| 1748 | |||||
| 1749 | # we replace, and repeat each line | ||||
| 1750 | for (my $i = 0 ; $i < $max ; $i++){ | ||||
| 1751 | my $newline = $line; | ||||
| 1752 | |||||
| 1753 | foreach my $tag ( $newline =~ /\[(\d{3}[\w|\d])\]/g ) { | ||||
| 1754 | $tag =~ /(.{3})(.)/; | ||||
| 1755 | |||||
| 1756 | if($marcrecord->field($1)){ | ||||
| 1757 | my @repl = $marcrecord->field($1)->subfield($2); | ||||
| 1758 | my $subfieldvalue = $repl[$i]; | ||||
| 1759 | |||||
| 1760 | if (! utf8::is_utf8($subfieldvalue)) { | ||||
| 1761 | utf8::decode($subfieldvalue); | ||||
| 1762 | } | ||||
| 1763 | |||||
| 1764 | $newline =~ s/\[$tag\]/$subfieldvalue/g; | ||||
| 1765 | } | ||||
| 1766 | } | ||||
| 1767 | $newsummary .= "$newline\n"; | ||||
| 1768 | } | ||||
| 1769 | } | ||||
| 1770 | |||||
| 1771 | $newsummary =~ s/\[(.*?)]//g; | ||||
| 1772 | $newsummary =~ s/\n/<br\/>/g; | ||||
| 1773 | $oldbiblio->{summary} = $newsummary; | ||||
| 1774 | } | ||||
| 1775 | |||||
| 1776 | # Pull out the items fields | ||||
| 1777 | 25 | 131µs | 25 | 7.00ms | my @fields = $marcrecord->field($itemtag); # spent 7.00ms making 25 calls to MARC::Record::field, avg 280µs/call |
| 1778 | 25 | 216µs | 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 | 25 | 23µs | my $analyticsfield = '773'; | ||
| 1781 | 25 | 45µs | if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') { | ||
| 1782 | $analyticsfield = '773'; | ||||
| 1783 | } elsif ($marcflavor eq 'UNIMARC') { | ||||
| 1784 | $analyticsfield = '461'; | ||||
| 1785 | } | ||||
| 1786 | 25 | 184µs | 25 | 7.15ms | foreach my $hostfield ( $marcrecord->field($analyticsfield)) { # spent 7.15ms making 25 calls to MARC::Record::field, avg 286µs/call |
| 1787 | 9 | 72µ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 | 30µs | 9 | 107µs | my $linkeditemnumber = $hostfield->subfield("9"); # spent 107µs making 9 calls to MARC::Field::subfield, avg 12µs/call |
| 1789 | 9 | 27µs | if(!$hostbiblionumber eq undef){ | ||
| 1790 | my $hostbiblio = GetMarcBiblio($hostbiblionumber, 1); | ||||
| 1791 | my ($itemfield, undef) = GetMarcFromKohaField( 'items.itemnumber', GetFrameworkCode($hostbiblionumber) ); | ||||
| 1792 | if(!$hostbiblio eq undef){ | ||||
| 1793 | my @hostitems = $hostbiblio->field($itemfield); | ||||
| 1794 | foreach my $hostitem (@hostitems){ | ||||
| 1795 | if ($hostitem->subfield("9") eq $linkeditemnumber){ | ||||
| 1796 | my $linkeditem =$hostitem; | ||||
| 1797 | # append linked items if they exist | ||||
| 1798 | if (!$linkeditem eq undef){ | ||||
| 1799 | push (@fields, $linkeditem);} | ||||
| 1800 | } | ||||
| 1801 | } | ||||
| 1802 | } | ||||
| 1803 | } | ||||
| 1804 | } | ||||
| 1805 | |||||
| 1806 | # Setting item statuses for display | ||||
| 1807 | 25 | 16µs | my @available_items_loop; | ||
| 1808 | 25 | 10µs | my @onloan_items_loop; | ||
| 1809 | 25 | 10µs | my @other_items_loop; | ||
| 1810 | |||||
| 1811 | 25 | 8µs | my $available_items; | ||
| 1812 | 25 | 7µs | my $onloan_items; | ||
| 1813 | 25 | 10µs | my $other_items; | ||
| 1814 | |||||
| 1815 | 25 | 15µs | my $ordered_count = 0; | ||
| 1816 | 25 | 9µs | my $available_count = 0; | ||
| 1817 | 25 | 8µs | my $onloan_count = 0; | ||
| 1818 | 25 | 9µs | my $longoverdue_count = 0; | ||
| 1819 | 25 | 14µs | my $other_count = 0; | ||
| 1820 | 25 | 12µs | my $wthdrawn_count = 0; | ||
| 1821 | 25 | 18µs | my $itemlost_count = 0; | ||
| 1822 | 25 | 13µs | my $hideatopac_count = 0; | ||
| 1823 | 25 | 8µs | my $itembinding_count = 0; | ||
| 1824 | 25 | 7µs | my $itemdamaged_count = 0; | ||
| 1825 | 25 | 6µs | my $item_in_transit_count = 0; | ||
| 1826 | 25 | 8µs | my $can_place_holds = 0; | ||
| 1827 | 25 | 70µs | my $item_onhold_count = 0; | ||
| 1828 | 25 | 23µs | my $items_count = scalar(@fields); | ||
| 1829 | 25 | 141µs | 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 | 25 | 85µs | my $maxitems = $maxitems_pref ? $maxitems_pref - 1 : 1; | ||
| 1831 | 25 | 17µs | my @hiddenitems; # hidden itemnumbers based on OpacHiddenItems syspref | ||
| 1832 | |||||
| 1833 | # loop through every item | ||||
| 1834 | 25 | 65µs | foreach my $field (@fields) { | ||
| 1835 | 32 | 14µs | my $item; | ||
| 1836 | |||||
| 1837 | # populate the items hash | ||||
| 1838 | 32 | 554µs | 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 | 32 | 170µs | $item->{description} = $itemtypes{ $item->{itype} }{description}; | ||
| 1842 | |||||
| 1843 | # OPAC hidden items | ||||
| 1844 | 32 | 51µs | if ($is_opac) { | ||
| 1845 | # hidden because lost | ||||
| 1846 | 32 | 24µs | if ($hidelostitems && $item->{itemlost}) { | ||
| 1847 | $hideatopac_count++; | ||||
| 1848 | next; | ||||
| 1849 | } | ||||
| 1850 | # hidden based on OpacHiddenItems syspref | ||||
| 1851 | 32 | 235µs | 32 | 125ms | my @hi = C4::Items::GetHiddenItemnumbers($item); # spent 125ms making 32 calls to C4::Items::GetHiddenItemnumbers, avg 3.90ms/call |
| 1852 | 32 | 44µs | if (scalar @hi) { | ||
| 1853 | push @hiddenitems, @hi; | ||||
| 1854 | $hideatopac_count++; | ||||
| 1855 | next; | ||||
| 1856 | } | ||||
| 1857 | } | ||||
| 1858 | |||||
| 1859 | 32 | 325µs | 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 | 145µs | 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 | 32 | 290µs | if ($item->{$hbranch}) { | ||
| 1864 | $item->{'branchname'} = $branches{$item->{$hbranch}}; | ||||
| 1865 | } | ||||
| 1866 | elsif ($item->{$otherbranch}) { # Last resort | ||||
| 1867 | $item->{'branchname'} = $branches{$item->{$otherbranch}}; | ||||
| 1868 | } | ||||
| 1869 | |||||
| 1870 | 32 | 294µs | my $prefix = $item->{$hbranch} . '--' . $item->{location} . $item->{itype} . $item->{itemcallnumber}; | ||
| 1871 | # For each grouping of items (onloan, available, unavailable), we build a key to store relevant info about that item | ||||
| 1872 | 32 | 187µs | 32 | 302µs | my $userenv = C4::Context->userenv; # spent 302µs making 32 calls to C4::Context::userenv, avg 9µs/call |
| 1873 | 32 | 623µ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 | 32 | 134µs | if ( $item->{notforloan} < 0 ) { | ||
| 1897 | $ordered_count++; | ||||
| 1898 | } | ||||
| 1899 | |||||
| 1900 | # is item in transit? | ||||
| 1901 | 32 | 30µs | my $transfertwhen = ''; | ||
| 1902 | 32 | 29µs | my ($transfertfrom, $transfertto); | ||
| 1903 | |||||
| 1904 | # is item on the reserve shelf? | ||||
| 1905 | 32 | 25µs | my $reservestatus = ''; | ||
| 1906 | |||||
| 1907 | 32 | 157µ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 | 32 | 290µs | 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 | 32 | 359µs | 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 | 32 | 336µs | if ( $item->{wthdrawn} | ||
| 1931 | || $item->{itemlost} | ||||
| 1932 | || $item->{damaged} | ||||
| 1933 | || $item->{notforloan} | ||||
| 1934 | || $reservestatus eq 'Waiting' | ||||
| 1935 | || ($transfertwhen ne '')) | ||||
| 1936 | { | ||||
| 1937 | $wthdrawn_count++ if $item->{wthdrawn}; | ||||
| 1938 | $itemlost_count++ if $item->{itemlost}; | ||||
| 1939 | $itemdamaged_count++ if $item->{damaged}; | ||||
| 1940 | $item_in_transit_count++ if $transfertwhen ne ''; | ||||
| 1941 | $item_onhold_count++ if $reservestatus eq 'Waiting'; | ||||
| 1942 | $item->{status} = $item->{wthdrawn} . "-" . $item->{itemlost} . "-" . $item->{damaged} . "-" . $item->{notforloan}; | ||||
| 1943 | |||||
| 1944 | # can place hold on item ? | ||||
| 1945 | if ( !$item->{itemlost} ) { | ||||
| 1946 | if ( !$item->{wthdrawn} ){ | ||||
| 1947 | if ( $item->{damaged} ){ | ||||
| 1948 | if ( C4::Context->preference('AllowHoldsOnDamagedItems') ){ | ||||
| 1949 | # can place a hold on a damaged item if AllowHoldsOnDamagedItems is true | ||||
| 1950 | if ( ( !$item->{notforloan} || $item->{notforloan} < 0 ) ){ | ||||
| 1951 | # item is either for loan or has notforloan < 0 | ||||
| 1952 | $can_place_holds = 1; | ||||
| 1953 | } | ||||
| 1954 | } | ||||
| 1955 | } elsif ( $item->{notforloan} < 0 ) { | ||||
| 1956 | # item is not damaged and notforloan is < 0 | ||||
| 1957 | $can_place_holds = 1; | ||||
| 1958 | } | ||||
| 1959 | } | ||||
| 1960 | } | ||||
| 1961 | |||||
| 1962 | $other_count++; | ||||
| 1963 | |||||
| 1964 | my $key = $prefix . $item->{status}; | ||||
| 1965 | foreach (qw(wthdrawn itemlost damaged branchname itemcallnumber)) { | ||||
| 1966 | $other_items->{$key}->{$_} = $item->{$_}; | ||||
| 1967 | } | ||||
| 1968 | $other_items->{$key}->{intransit} = ( $transfertwhen ne '' ) ? 1 : 0; | ||||
| 1969 | $other_items->{$key}->{onhold} = ($reservestatus) ? 1 : 0; | ||||
| 1970 | $other_items->{$key}->{notforloan} = GetAuthorisedValueDesc('','',$item->{notforloan},'','',$notforloan_authorised_value) if $notforloan_authorised_value and $item->{notforloan}; | ||||
| 1971 | $other_items->{$key}->{count}++ if $item->{$hbranch}; | ||||
| 1972 | $other_items->{$key}->{location} = $shelflocations->{ $item->{location} }; | ||||
| 1973 | $other_items->{$key}->{description} = $item->{description}; | ||||
| 1974 | $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context, $itemtypes{ $item->{itype} }->{imageurl} ); | ||||
| 1975 | } | ||||
| 1976 | # item is available | ||||
| 1977 | else { | ||||
| 1978 | 32 | 28µs | $can_place_holds = 1; | ||
| 1979 | 32 | 27µs | $available_count++; | ||
| 1980 | 32 | 214µs | $available_items->{$prefix}->{count}++ if $item->{$hbranch}; | ||
| 1981 | 32 | 101µs | foreach (qw(branchname itemcallnumber description)) { | ||
| 1982 | 96 | 326µs | $available_items->{$prefix}->{$_} = $item->{$_}; | ||
| 1983 | } | ||||
| 1984 | 32 | 185µs | $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} }; | ||
| 1985 | 32 | 478µs | 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 | 25 | 50µs | if ($items_count > 0 && $hideatopac_count == $items_count) { | ||
| 1992 | next; | ||||
| 1993 | } | ||||
| 1994 | |||||
| 1995 | 25 | 39µs | my ( $availableitemscount, $onloanitemscount, $otheritemscount ); | ||
| 1996 | 25 | 446µs | 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 | 196µs | 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 | 244µs | 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 | 30 | 28µs | (++$availableitemscount > $maxitems) and last; | ||
| 2006 | 29 | 91µs | 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 | 203µs | 25 | 114ms | SetUTF8Flag($marcrecord); # spent 114ms making 25 calls to C4::Charset::SetUTF8Flag, avg 4.56ms/call |
| 2012 | 25 | 35µs | warn $marcrecord->as_formatted if $DEBUG; | ||
| 2013 | 25 | 77µs | my $interface = $search_context eq 'opac' ? 'OPAC' : ''; | ||
| 2014 | 25 | 619µs | 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 | 179µs | 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 | 25 | 14µs | $oldbiblio->{norequests} = 1 unless $can_place_holds; | ||
| 2026 | 25 | 34µs | $oldbiblio->{itemsplural} = 1 if $items_count > 1; | ||
| 2027 | 25 | 52µs | $oldbiblio->{items_count} = $items_count; | ||
| 2028 | 25 | 65µs | $oldbiblio->{available_items_loop} = \@available_items_loop; | ||
| 2029 | 25 | 53µs | $oldbiblio->{onloan_items_loop} = \@onloan_items_loop; | ||
| 2030 | 25 | 39µs | $oldbiblio->{other_items_loop} = \@other_items_loop; | ||
| 2031 | 25 | 77µs | $oldbiblio->{availablecount} = $available_count; | ||
| 2032 | 25 | 17µs | $oldbiblio->{availableplural} = 1 if $available_count > 1; | ||
| 2033 | 25 | 37µs | $oldbiblio->{onloancount} = $onloan_count; | ||
| 2034 | 25 | 12µs | $oldbiblio->{onloanplural} = 1 if $onloan_count > 1; | ||
| 2035 | 25 | 38µs | $oldbiblio->{othercount} = $other_count; | ||
| 2036 | 25 | 14µs | $oldbiblio->{otherplural} = 1 if $other_count > 1; | ||
| 2037 | 25 | 40µs | $oldbiblio->{wthdrawncount} = $wthdrawn_count; | ||
| 2038 | 25 | 37µs | $oldbiblio->{itemlostcount} = $itemlost_count; | ||
| 2039 | 25 | 32µs | $oldbiblio->{damagedcount} = $itemdamaged_count; | ||
| 2040 | 25 | 33µs | $oldbiblio->{intransitcount} = $item_in_transit_count; | ||
| 2041 | 25 | 31µs | $oldbiblio->{onholdcount} = $item_onhold_count; | ||
| 2042 | 25 | 35µs | $oldbiblio->{orderedcount} = $ordered_count; | ||
| 2043 | |||||
| 2044 | 25 | 90µs | 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 | 25 | 2.32ms | push( @newresults, $oldbiblio ); | ||
| 2073 | 1 | 800ns | } | ||
| 2074 | |||||
| 2075 | 1 | 242µs | 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 | 1 | 2µs | my ($zconns, $results, $callback) = @_; | ||
| 2347 | 1 | 52µ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 | 54µs | 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 | 11 | 28µs | 11 | 61µs | if ( $ev == ZOOM::Event::ZEND ) { # spent 61µs making 11 calls to ZOOM::Event::ZEND, avg 6µs/call |
| 2350 | 1 | 2µs | next unless $results->[ $i - 1 ]; | ||
| 2351 | 1 | 7µs | 1 | 71µs | my $size = $results->[ $i - 1 ]->size(); # spent 71µs making 1 call to ZOOM::ResultSet::size |
| 2352 | 1 | 8µs | 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 | 1 | 7µs | 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 |