MT 2069 : MoveItemFromBiblio now checks for orders
[koha.git] / catalogue / search.pl
1 #!/usr/bin/perl
2 # Script to perform searching
3 # For documentation try 'perldoc /path/to/search'
4 #
5 # Copyright 2006 LibLime
6 #
7 # This file is part of Koha
8 #
9 # Koha is free software; you can redistribute it and/or modify it under the
10 # terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any later
12 # version.
13 #
14 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along with
19 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
20 # Suite 330, Boston, MA  02111-1307 USA
21
22 =head1 NAME
23
24 search - a search script for finding records in a Koha system (Version 3)
25
26 =head1 OVERVIEW
27
28 This script utilizes a new search API for Koha 3. It is designed to be 
29 simple to use and configure, yet capable of performing feats like stemming,
30 field weighting, relevance ranking, support for multiple  query language
31 formats (CCL, CQL, PQF), full support for the bib1 attribute set, extended
32 attribute sets defined in Zebra profiles, access to the full range of Z39.50
33 and SRU query options, federated searches on Z39.50/SRU targets, etc.
34
35 The API as represented in this script is mostly sound, even if the individual
36 functions in Search.pm and Koha.pm need to be cleaned up. Of course, you are
37 free to disagree :-)
38
39 I will attempt to describe what is happening at each part of this script.
40 -- Joshua Ferraro <jmf AT liblime DOT com>
41
42 =head2 INTRO
43
44 This script performs two functions:
45
46 =over 
47
48 =item 1. interacts with Koha to retrieve and display the results of a search
49
50 =item 2. loads the advanced search page
51
52 =back
53
54 These two functions share many of the same variables and modules, so the first
55 task is to load what they have in common and determine which template to use.
56 Once determined, proceed to only load the variables and procedures necessary
57 for that function.
58
59 =head2 LOADING ADVANCED SEARCH PAGE
60
61 This is fairly straightforward, and I won't go into detail ;-)
62
63 =head2 PERFORMING A SEARCH
64
65 If we're performing a search, this script  performs three primary
66 operations:
67
68 =over 
69
70 =item 1. builds query strings (yes, plural)
71
72 =item 2. perform the search and return the results array
73
74 =item 3. build the HTML for output to the template
75
76 =back
77
78 There are several additional secondary functions performed that I will
79 not cover in detail.
80
81 =head3 1. Building Query Strings
82     
83 There are several types of queries needed in the process of search and retrieve:
84
85 =over
86
87 =item 1 $query - the fully-built query passed to zebra
88
89 This is the most complex query that needs to be built. The original design goal 
90 was to use a custom CCL2PQF query parser to translate an incoming CCL query into
91 a multi-leaf query to pass to Zebra. It needs to be multi-leaf to allow field 
92 weighting, koha-specific relevance ranking, and stemming. When I have a chance 
93 I'll try to flesh out this section to better explain.
94
95 This query incorporates query profiles that aren't compatible with most non-Zebra 
96 Z39.50 targets to acomplish the field weighting and relevance ranking.
97
98 =item 2 $simple_query - a simple query that doesn't contain the field weighting,
99 stemming, etc., suitable to pass off to other search targets
100
101 This query is just the user's query expressed in CCL CQL, or PQF for passing to a 
102 non-zebra Z39.50 target (one that doesn't support the extended profile that Zebra does).
103
104 =item 3 $query_cgi - passed to the template / saved for future refinements of 
105 the query (by user)
106
107 This is a simple string that completely expresses the query as a CGI string that
108 can be used for future refinements of the query or as a part of a history feature.
109
110 =item 4 $query_desc - Human search description - what the user sees in search
111 feedback area
112
113 This is a simple string that is human readable. It will contain '=', ',', etc.
114
115 =back
116
117 =head3 2. Perform the Search
118
119 This section takes the query strings and performs searches on the named servers,
120 including the Koha Zebra server, stores the results in a deeply nested object, 
121 builds 'faceted results', and returns these objects.
122
123 =head3 3. Build HTML
124
125 The final major section of this script takes the objects collected thusfar and 
126 builds the HTML for output to the template and user.
127
128 =head3 Additional Notes
129
130 Not yet completed...
131
132 =cut
133
134 use strict;            # always use
135
136 ## STEP 1. Load things that are used in both search page and
137 # results page and decide which template to load, operations 
138 # to perform, etc.
139
140 ## load Koha modules
141 use C4::Context;
142 use C4::Output;
143 use C4::Auth qw(:DEFAULT get_session);
144 use C4::Search;
145 use C4::Languages qw(getAllLanguages);
146 use C4::Koha;
147 use C4::VirtualShelves qw(GetRecentShelves);
148 use POSIX qw(ceil floor);
149 use C4::Branch; # GetBranches
150
151 # create a new CGI object
152 # FIXME: no_undef_params needs to be tested
153 use CGI qw('-no_undef_params');
154 my $cgi = new CGI;
155
156 my ($template,$borrowernumber,$cookie);
157
158 # decide which template to use
159 my $template_name;
160 my $template_type;
161 my @params = $cgi->param("limit");
162 if ((@params>=1) || ($cgi->param("q")) || ($cgi->param('multibranchlimit')) || ($cgi->param('limit-yr')) ) {
163     $template_name = 'catalogue/results.tmpl';
164 }
165 else {
166     $template_name = 'catalogue/advsearch.tmpl';
167     $template_type = 'advsearch';
168 }
169 # load the template
170 ($template, $borrowernumber, $cookie) = get_template_and_user({
171     template_name => $template_name,
172     query => $cgi,
173     type => "intranet",
174     authnotrequired => 0,
175     flagsrequired   => { catalogue => 1 },
176     }
177 );
178 if (C4::Context->preference("marcflavour") eq "UNIMARC" ) {
179     $template->param('UNIMARC' => 1);
180 }
181
182 ## URI Re-Writing
183 # Deprecated, but preserved because it's interesting :-)
184 # The same thing can be accomplished with mod_rewrite in
185 # a more elegant way
186 #
187 #my $rewrite_flag;
188 #my $uri = $cgi->url(-base => 1);
189 #my $relative_url = $cgi->url(-relative=>1);
190 #$uri.="/".$relative_url."?";
191 #warn "URI:$uri";
192 #my @cgi_params_list = $cgi->param();
193 #my $url_params = $cgi->Vars;
194 #
195 #for my $each_param_set (@cgi_params_list) {
196 #    $uri.= join "",  map "\&$each_param_set=".$_, split("\0",$url_params->{$each_param_set}) if $url_params->{$each_param_set};
197 #}
198 #warn "New URI:$uri";
199 # Only re-write a URI if there are params or if it already hasn't been re-written
200 #unless (($cgi->param('r')) || (!$cgi->param()) ) {
201 #    print $cgi->redirect(     -uri=>$uri."&r=1",
202 #                            -cookie => $cookie);
203 #    exit;
204 #}
205
206 # load the branches
207 my $branches = GetBranches();
208 my @branch_loop;
209
210 # we need to know the borrower branch code to set a default branch
211 my $borrowerbranchcode = C4::Context->userenv->{'branch'};
212
213 for my $branch_hash (sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} } keys %$branches) {
214     # if independantbranches is activated, set the default branch to the borrower branch
215     my $selected = (C4::Context->preference("independantbranches") and ($borrowerbranchcode eq $branch_hash)) ? 1 : undef;
216     push @branch_loop, {value => "$branch_hash" , branchname => $branches->{$branch_hash}->{'branchname'}, selected => $selected};
217 }
218
219 my $categories = GetBranchCategories(undef,'searchdomain');
220
221 $template->param(branchloop => \@branch_loop, searchdomainloop => $categories);
222
223 # load the Type stuff
224 # load the Type stuff
225 my $itemtypes = GetItemTypes;
226 # the index parameter is different for item-level itemtypes
227 my $itype_or_itemtype = (C4::Context->preference("item-level_itypes"))?'itype':'itemtype';
228 my @itemtypesloop;
229 my $selected=1;
230 my $cnt;
231 my $advanced_search_types = C4::Context->preference("AdvancedSearchTypes");
232
233 if (!$advanced_search_types or $advanced_search_types eq 'itemtypes') {                                                                 foreach my $thisitemtype ( sort {$itemtypes->{$a}->{'description'} cmp $itemtypes->{$b}->{'description'} } keys %$itemtypes ) {
234     my %row =(  number=>$cnt++,
235                 ccl => $itype_or_itemtype,
236                 code => $thisitemtype,
237                 selected => $selected,
238                 description => $itemtypes->{$thisitemtype}->{'description'},
239                 count5 => $cnt % 4,
240                 imageurl=> getitemtypeimagelocation( 'intranet', $itemtypes->{$thisitemtype}->{'imageurl'} ),
241             );
242         $selected = 0 if ($selected) ;
243         push @itemtypesloop, \%row;
244     }
245     $template->param(itemtypeloop => \@itemtypesloop);
246 } else {
247     my $advsearchtypes = GetAuthorisedValues($advanced_search_types);
248     for my $thisitemtype (sort {$a->{'lib'} cmp $b->{'lib'}} @$advsearchtypes) {
249         my %row =(
250                 number=>$cnt++,
251                 ccl => $advanced_search_types,
252                 code => $thisitemtype->{authorised_value},
253                 selected => $selected,
254                 description => $thisitemtype->{'lib'},
255                 count5 => $cnt % 4,
256                 imageurl=> getitemtypeimagelocation( 'intranet', $thisitemtype->{'imageurl'} ),
257             );
258         push @itemtypesloop, \%row;
259     }
260     $template->param(itemtypeloop => \@itemtypesloop);
261 }
262
263 # The following should only be loaded if we're bringing up the advanced search template
264 if ( $template_type eq 'advsearch' ) {
265
266     # load the servers (used for searching -- to do federated searching, etc.)
267     my $primary_servers_loop;# = displayPrimaryServers();
268     $template->param(outer_servers_loop =>  $primary_servers_loop,);
269     
270     my $secondary_servers_loop;
271     $template->param(outer_sup_servers_loop => $secondary_servers_loop,);
272
273     # set the default sorting
274     my $default_sort_by = C4::Context->preference('defaultSortField')."_".C4::Context->preference('defaultSortOrder')
275         if (C4::Context->preference('OPACdefaultSortField') && C4::Context->preference('OPACdefaultSortOrder'));
276     $template->param($default_sort_by => 1);
277
278     # determine what to display next to the search boxes (ie, boolean option
279     # shouldn't appear on the first one, scan indexes should, adding a new
280     # box should only appear on the last, etc.
281     my @search_boxes_array;
282     my $search_boxes_count = C4::Context->preference("OPACAdvSearchInputCount") || 3; # FIXME: using OPAC sysprefs?
283     # FIXME: all this junk can be done in TMPL using __first__ and __last__
284     for (my $i=1;$i<=$search_boxes_count;$i++) {
285         # if it's the first one, don't display boolean option, but show scan indexes
286         if ($i==1) {
287             push @search_boxes_array, {scan_index => 1};
288         }
289         # if it's the last one, show the 'add field' box
290         elsif ($i==$search_boxes_count) {
291             push @search_boxes_array,
292                 {
293                 boolean => 1,
294                 add_field => 1,
295                 };
296         }
297         else {
298             push @search_boxes_array,
299                 {
300                 boolean => 1,
301                 };
302         }
303
304     }
305     $template->param(uc(C4::Context->preference("marcflavour")) => 1,
306                       search_boxes_loop => \@search_boxes_array);
307
308     # load the language limits (for search)
309     my $languages_limit_loop = getAllLanguages();
310     $template->param(search_languages_loop => $languages_limit_loop,);
311
312     # use the global setting by default
313     if ( C4::Context->preference("expandedSearchOption") == 1) {
314         $template->param( expanded_options => C4::Context->preference("expandedSearchOption") );
315     }
316     # but let the user override it
317     if ( ($cgi->param('expanded_options') == 0) || ($cgi->param('expanded_options') == 1 ) ) {
318         $template->param( expanded_options => $cgi->param('expanded_options'));
319     }
320
321     $template->param(virtualshelves => C4::Context->preference("virtualshelves"));
322
323     output_html_with_http_headers $cgi, $cookie, $template->output;
324     exit;
325 }
326
327 ### OK, if we're this far, we're performing a search, not just loading the advanced search page
328
329 # Fetch the paramater list as a hash in scalar context:
330 #  * returns paramater list as tied hash ref
331 #  * we can edit the values by changing the key
332 #  * multivalued CGI paramaters are returned as a packaged string separated by "\0" (null)
333 my $params = $cgi->Vars;
334
335 # Params that can have more than one value
336 # sort by is used to sort the query
337 # in theory can have more than one but generally there's just one
338 my @sort_by;
339 my $default_sort_by = C4::Context->preference('defaultSortField')."_".C4::Context->preference('defaultSortOrder') 
340     if (C4::Context->preference('defaultSortField') && C4::Context->preference('defaultSortOrder'));
341
342 @sort_by = split("\0",$params->{'sort_by'}) if $params->{'sort_by'};
343 $sort_by[0] = $default_sort_by unless $sort_by[0];
344 foreach my $sort (@sort_by) {
345     $template->param($sort => 1);
346 }
347 $template->param('sort_by' => $sort_by[0]);
348
349 # Use the servers defined, or just search our local catalog(default)
350 my @servers;
351 @servers = split("\0",$params->{'server'}) if $params->{'server'};
352 unless (@servers) {
353     #FIXME: this should be handled using Context.pm
354     @servers = ("biblioserver");
355     # @servers = C4::Context->config("biblioserver");
356 }
357 # operators include boolean and proximity operators and are used
358 # to evaluate multiple operands
359 my @operators;
360 @operators = split("\0",$params->{'op'}) if $params->{'op'};
361
362 # indexes are query qualifiers, like 'title', 'author', etc. They
363 # can be single or multiple parameters separated by comma: kw,right-Truncation 
364 my @indexes;
365 @indexes = split("\0",$params->{'idx'});
366
367 # if a simple index (only one)  display the index used in the top search box
368 if ($indexes[0] && !$indexes[1]) {
369     $template->param("ms_".$indexes[0] => 1);}
370
371
372 # an operand can be a single term, a phrase, or a complete ccl query
373 my @operands;
374 @operands = split("\0",$params->{'q'}) if $params->{'q'};
375
376 # limits are use to limit to results to a pre-defined category such as branch or language
377 my @limits;
378 @limits = split("\0",$params->{'limit'}) if $params->{'limit'};
379
380 if($params->{'multibranchlimit'}) {
381 push @limits, join(" or ", map { "branch: $_ "}  @{GetBranchesInCategory($params->{'multibranchlimit'})}) ;
382 }
383
384 my $available;
385 foreach my $limit(@limits) {
386     if ($limit =~/available/) {
387         $available = 1;
388     }
389 }
390 $template->param(available => $available);
391
392 # append year limits if they exist
393 my $limit_yr;
394 my $limit_yr_value;
395 if ($params->{'limit-yr'}) {
396     if ($params->{'limit-yr'} =~ /\d{4}-\d{4}/) {
397         my ($yr1,$yr2) = split(/-/, $params->{'limit-yr'});
398         $limit_yr = "yr,st-numeric,ge=$yr1 and yr,st-numeric,le=$yr2";
399         $limit_yr_value = "$yr1-$yr2";
400     }
401     elsif ($params->{'limit-yr'} =~ /\d{4}/) {
402         $limit_yr = "yr,st-numeric=$params->{'limit-yr'}";
403         $limit_yr_value = $params->{'limit-yr'};
404     }
405     push @limits,$limit_yr;
406     #FIXME: Should return a error to the user, incorect date format specified
407 }
408
409 # convert indexes and operands to corresponding parameter names for the z3950 search
410 # $ %z3950p will be a hash ref if the indexes are present (advacned search), otherwise undef
411 my $z3950par;
412 my $indexes2z3950 = {
413         kw=>'title', au=>'author', 'au,phr'=>'author', nb=>'isbn', ns=>'issn',
414         'lcn,phr'=>'dewey', su=>'subject', 'su,phr'=>'subject', 
415         ti=>'title', 'ti,phr'=>'title', se=>'title'
416 };
417 for (my $ii = 0; $ii < @operands; ++$ii)
418 {
419         my $name = $indexes2z3950->{$indexes[$ii]};
420         if (defined $name && defined $operands[$ii])
421         {
422                 $z3950par ||= {};
423                 $z3950par->{$name} = $operands[$ii] if !exists $z3950par->{$name};
424         }
425 }
426
427
428 # Params that can only have one value
429 my $scan = $params->{'scan'};
430 my $count = C4::Context->preference('numSearchResults') || 20;
431 my $results_per_page = $params->{'count'} || $count;
432 my $offset = $params->{'offset'} || 0;
433 my $page = $cgi->param('page') || 1;
434 #my $offset = ($page-1)*$results_per_page;
435 my $hits;
436 my $expanded_facet = $params->{'expand'};
437
438 # Define some global variables
439 my ( $error,$query,$simple_query,$query_cgi,$query_desc,$limit,$limit_cgi,$limit_desc,$stopwords_removed,$query_type);
440
441 my @results;
442
443 ## I. BUILD THE QUERY
444 my $lang = C4::Output::getlanguagecookie($cgi);
445 ( $error,$query,$simple_query,$query_cgi,$query_desc,$limit,$limit_cgi,$limit_desc,$stopwords_removed,$query_type) = buildQuery(\@operators,\@operands,\@indexes,\@limits,\@sort_by,$scan,$lang);
446
447 ## parse the query_cgi string and put it into a form suitable for <input>s
448 my @query_inputs;
449 my $scan_index_to_use;
450
451 for my $this_cgi ( split('&',$query_cgi) ) {
452     next unless $this_cgi;
453     $this_cgi =~ m/(.*=)(.*)/;
454     my $input_name = $1;
455     my $input_value = $2;
456     $input_name =~ s/=$//;
457     push @query_inputs, { input_name => $input_name, input_value => $input_value };
458         if ($input_name eq 'idx') {
459         $scan_index_to_use = $input_value; # unless $scan_index_to_use;
460         }
461 }
462 $template->param ( QUERY_INPUTS => \@query_inputs,
463                    scan_index_to_use => $scan_index_to_use );
464
465 ## parse the limit_cgi string and put it into a form suitable for <input>s
466 my @limit_inputs;
467 for my $this_cgi ( split('&',$limit_cgi) ) {
468     next unless $this_cgi;
469     # handle special case limit-yr
470     if ($this_cgi =~ /yr,st-numeric/) {
471         push @limit_inputs, { input_name => 'limit-yr', input_value => $limit_yr_value };   
472         next;
473     }
474     $this_cgi =~ m/(.*=)(.*)/;
475     my $input_name = $1;
476     my $input_value = $2;
477     $input_name =~ s/=$//;
478     push @limit_inputs, { input_name => $input_name, input_value => $input_value };
479 }
480 $template->param ( LIMIT_INPUTS => \@limit_inputs );
481
482 ## II. DO THE SEARCH AND GET THE RESULTS
483 my $total; # the total results for the whole set
484 my $facets; # this object stores the faceted results that display on the left-hand of the results page
485 my @results_array;
486 my $results_hashref;
487
488 if (C4::Context->preference('NoZebra')) {
489     $query=~s/yr(:|=)\s*([\d]{1,4})-([\d]{1,4})/(yr>=$2 and yr<=$3)/g;
490     $simple_query=~s/yr\s*(:|=)([\d]{1,4})-([\d]{1,4})/(yr>=$2 and yr<=$3)/g;
491     # warn $query; 
492     eval {
493         ($error, $results_hashref, $facets) = NZgetRecords($query,$simple_query,\@sort_by,\@servers,$results_per_page,$offset,$expanded_facet,$branches,$query_type,$scan);
494     };
495 } else {
496     eval {
497         ($error, $results_hashref, $facets) = getRecords($query,$simple_query,\@sort_by,\@servers,$results_per_page,$offset,$expanded_facet,$branches,$query_type,$scan);
498     };
499 }
500 if ($@ || $error) {
501     $template->param(query_error => $error.$@);
502     output_html_with_http_headers $cgi, $cookie, $template->output;
503     exit;
504 }
505
506 # At this point, each server has given us a result set
507 # now we build that set for template display
508 my @sup_results_array;
509 for (my $i=0;$i<@servers;$i++) {
510     my $server = $servers[$i];
511     if ($server =~/biblioserver/) { # this is the local bibliographic server
512         $hits = $results_hashref->{$server}->{"hits"};
513         my $page = $cgi->param('page') || 0;
514         my @newresults = searchResults( $query_desc,$hits,$results_per_page,$offset,$scan,@{$results_hashref->{$server}->{"RECORDS"}});
515         $total = $total + $results_hashref->{$server}->{"hits"};
516         ## If there's just one result, redirect to the detail page
517         if ($total == 1) {         
518             my $biblionumber = $newresults[0]->{biblionumber};
519                         my $defaultview = C4::Context->preference('IntranetBiblioDefaultView');
520                         my $views = { C4::Search::enabled_staff_search_views }; 
521             if ($defaultview eq 'isbd' && $views->{can_view_ISBD}) {
522                 print $cgi->redirect("/cgi-bin/koha/catalogue/ISBDdetail.pl?biblionumber=$biblionumber");
523             } elsif  ($defaultview eq 'marc' && $views->{can_view_MARC}) {
524                 print $cgi->redirect("/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=$biblionumber");
525             } elsif  ($defaultview eq 'labeled_marc' && $views->{can_view_labeledMARC}) {
526                 print $cgi->redirect("/cgi-bin/koha/catalogue/labeledMARCdetail.pl?biblionumber=$biblionumber");
527             } else {
528                 print $cgi->redirect("/cgi-bin/koha/catalogue/detail.pl?biblionumber=$biblionumber");
529             } 
530             exit;
531         }
532
533
534         if ($hits) {
535             $template->param(total => $hits);
536             my $limit_cgi_not_availablity = $limit_cgi;
537             $limit_cgi_not_availablity =~ s/&limit=available//g;
538             $template->param(limit_cgi_not_availablity => $limit_cgi_not_availablity);
539             $template->param(limit_cgi => $limit_cgi);
540             $template->param(query_cgi => $query_cgi);
541             $template->param(query_desc => $query_desc);
542             $template->param(limit_desc => $limit_desc);
543                         $template->param (z3950_search_params => C4::Search::z3950_search_args($query_desc));
544             if ($query_desc || $limit_desc) {
545                 $template->param(searchdesc => 1);
546             }
547             $template->param(stopwords_removed => "@$stopwords_removed") if $stopwords_removed;
548             $template->param(results_per_page =>  $results_per_page);
549             $template->param(SEARCH_RESULTS => \@newresults);
550
551             ## FIXME: add a global function for this, it's better than the current global one
552             ## Build the page numbers on the bottom of the page
553             my @page_numbers;
554             # total number of pages there will be
555             my $pages = ceil($hits / $results_per_page);
556             # default page number
557             my $current_page_number = 1;
558             $current_page_number = ($offset / $results_per_page + 1) if $offset;
559             my $previous_page_offset = $offset - $results_per_page unless ($offset - $results_per_page <0);
560             my $next_page_offset = $offset + $results_per_page;
561             # If we're within the first 10 pages, keep it simple
562             #warn "current page:".$current_page_number;
563             if ($current_page_number < 10) {
564                 # just show the first 10 pages
565                 # Loop through the pages
566                 my $pages_to_show = 10;
567                 $pages_to_show = $pages if $pages<10;
568                 for (my $i=1; $i<=$pages_to_show;$i++) {
569                     # the offset for this page
570                     my $this_offset = (($i*$results_per_page)-$results_per_page);
571                     # the page number for this page
572                     my $this_page_number = $i;
573                     # it should only be highlighted if it's the current page
574                     my $highlight = 1 if ($this_page_number == $current_page_number);
575                     # put it in the array
576                     push @page_numbers, { offset => $this_offset, pg => $this_page_number, highlight => $highlight, sort_by => join " ",@sort_by };
577                                 
578                 }
579                         
580             }
581
582             # now, show twenty pages, with the current one smack in the middle
583             else {
584                 for (my $i=$current_page_number; $i<=($current_page_number + 20 );$i++) {
585                     my $this_offset = ((($i-9)*$results_per_page)-$results_per_page);
586                     my $this_page_number = $i-9;
587                     my $highlight = 1 if ($this_page_number == $current_page_number);
588                     if ($this_page_number <= $pages) {
589                         push @page_numbers, { offset => $this_offset, pg => $this_page_number, highlight => $highlight, sort_by => join " ",@sort_by };
590                     }
591                 }
592             }
593             # FIXME: no previous_page_offset when pages < 2
594             $template->param(   PAGE_NUMBERS => \@page_numbers,
595                                 previous_page_offset => $previous_page_offset) unless $pages < 2;
596             $template->param(   next_page_offset => $next_page_offset) unless $pages eq $current_page_number;
597         }
598
599
600         # no hits
601         else {
602             $template->param(searchdesc => 1,query_desc => $query_desc,limit_desc => $limit_desc);
603                         $template->param (z3950_search_params => C4::Search::z3950_search_args($z3950par || $query_desc));
604         }
605
606     } # end of the if local
607
608     # asynchronously search the authority server
609     elsif ($server =~/authorityserver/) { # this is the local authority server
610         my @inner_sup_results_array;
611         for my $sup_record ( @{$results_hashref->{$server}->{"RECORDS"}} ) {
612             my $marc_record_object = MARC::Record->new_from_usmarc($sup_record);
613             # warn "Authority Found: ".$marc_record_object->as_formatted();
614             push @inner_sup_results_array, {
615                 'title' => $marc_record_object->field(100)->subfield('a'),
616                 'link' => "&amp;idx=an&amp;q=".$marc_record_object->field('001')->as_string(),
617             };
618         }
619         push @sup_results_array, {  servername => $server, 
620                                     inner_sup_results_loop => \@inner_sup_results_array} if @inner_sup_results_array;
621     }
622     # FIXME: can add support for other targets as needed here
623     $template->param(           outer_sup_results_loop => \@sup_results_array);
624 } #/end of the for loop
625 #$template->param(FEDERATED_RESULTS => \@results_array);
626
627
628 $template->param(
629             #classlist => $classlist,
630             total => $total,
631             opacfacets => 1,
632             facets_loop => $facets,
633             scan => $scan,
634             search_error => $error,
635 );
636
637 if ($query_desc || $limit_desc) {
638     $template->param(searchdesc => 1);
639 }
640
641 # VI. BUILD THE TEMPLATE
642
643 # Build drop-down list for 'Add To:' menu...
644
645 my $row_count = 10; # FIXME:This probably should be a syspref
646 my ($pubshelves, $total) = GetRecentShelves(2, $row_count, undef);
647 my ($barshelves, $total) = GetRecentShelves(1, $row_count, $borrowernumber);
648
649 my @pubshelves = @{$pubshelves};
650 my @barshelves = @{$barshelves};
651
652 if (@pubshelves) {
653         $template->param( addpubshelves     => scalar (@pubshelves));
654         $template->param( addpubshelvesloop => @pubshelves);
655 }
656
657 if (@barshelves) {
658         $template->param( addbarshelves     => scalar (@barshelves));
659         $template->param( addbarshelvesloop => @barshelves);
660 }
661
662
663
664 output_html_with_http_headers $cgi, $cookie, $template->output;