Bug 24171: Preserve auto_renew when triggering itemBarcodeFallbackSearch
[koha.git] / circ / circulation.pl
1 #!/usr/bin/perl
2
3 # script to execute issuing of books
4
5 # Copyright 2000-2002 Katipo Communications
6 # copyright 2010 BibLibre
7 # Copyright 2011 PTFS-Europe Ltd.
8 # Copyright 2012 software.coop and MJ Ray
9 #
10 # This file is part of Koha.
11 #
12 # Koha is free software; you can redistribute it and/or modify it
13 # under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # Koha is distributed in the hope that it will be useful, but
18 # WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with Koha; if not, see <http://www.gnu.org/licenses>.
24
25 # FIXME There are too many calls to Koha::Patrons->find in this script
26
27 use Modern::Perl;
28 use CGI qw ( -utf8 );
29 use DateTime;
30 use DateTime::Duration;
31 use Scalar::Util qw( looks_like_number );
32 use C4::Output;
33 use C4::Print;
34 use C4::Auth qw/:DEFAULT get_session haspermission/;
35 use C4::Koha;   # GetPrinter
36 use C4::Circulation;
37 use C4::Utils::DataTables::Members;
38 use C4::Members;
39 use C4::Biblio;
40 use C4::Search;
41 use MARC::Record;
42 use C4::Reserves;
43 use Koha::Holds;
44 use C4::Context;
45 use CGI::Session;
46 use Koha::AuthorisedValues;
47 use Koha::CsvProfiles;
48 use Koha::Patrons;
49 use Koha::Patron::Debarments qw(GetDebarments);
50 use Koha::DateUtils;
51 use Koha::Database;
52 use Koha::BiblioFrameworks;
53 use Koha::Items;
54 use Koha::Patron::Messages;
55 use Koha::SearchEngine;
56 use Koha::SearchEngine::Search;
57 use Koha::Patron::Modifications;
58
59 use Date::Calc qw(
60   Today
61   Add_Delta_Days
62   Date_to_Days
63 );
64 use List::MoreUtils qw/uniq/;
65
66 #
67 # PARAMETERS READING
68 #
69 my $query = new CGI;
70
71 my $override_high_holds     = $query->param('override_high_holds');
72 my $override_high_holds_tmp = $query->param('override_high_holds_tmp');
73
74 my $sessionID = $query->cookie("CGISESSID") ;
75 my $session = get_session($sessionID);
76 if (!C4::Context->userenv){
77     if ($session->param('branch') eq 'NO_LIBRARY_SET'){
78         # no branch set we can't issue
79         print $query->redirect("/cgi-bin/koha/circ/selectbranchprinter.pl");
80         exit;
81     }
82 }
83
84 my $barcodes = [];
85 my $barcode =  $query->param('barcode');
86 my $findborrower;
87 my $autoswitched;
88 my $borrowernumber = $query->param('borrowernumber');
89
90 if (C4::Context->preference("AutoSwitchPatron") && $barcode) {
91     if (Koha::Patrons->search( { cardnumber => $barcode} )->count() > 0) {
92         $findborrower = $barcode;
93         undef $barcode;
94         undef $borrowernumber;
95         $autoswitched = 1;
96     }
97 }
98 $findborrower ||= $query->param('findborrower') || q{};
99 $findborrower =~ s|,| |g;
100
101 # Barcode given by user could be '0'
102 if ( $barcode || ( defined($barcode) && $barcode eq '0' ) ) {
103     $barcodes = [ $barcode ];
104 } else {
105     my $filefh = $query->upload('uploadfile');
106     if ( $filefh ) {
107         while ( my $content = <$filefh> ) {
108             $content =~ s/[\r\n]*$//g;
109             push @$barcodes, $content if $content;
110         }
111     } elsif ( my $list = $query->param('barcodelist') ) {
112         push @$barcodes, split( /\s\n/, $list );
113         $barcodes = [ map { $_ =~ /^\s*$/ ? () : $_ } @$barcodes ];
114     } else {
115         @$barcodes = $query->multi_param('barcodes');
116     }
117 }
118
119 $barcodes = [ uniq @$barcodes ];
120
121 my $template_name = q|circ/circulation.tt|;
122 my $patron = $borrowernumber ? Koha::Patrons->find( $borrowernumber ) : undef;
123 my $batch = $query->param('batch');
124 my $batch_allowed = 0;
125 if ( $batch && C4::Context->preference('BatchCheckouts') ) {
126     $template_name = q|circ/circulation_batch_checkouts.tt|;
127     my @batch_category_codes = split '\|', C4::Context->preference('BatchCheckoutsValidCategories');
128     my $categorycode = $patron->categorycode;
129     if ( $categorycode && grep {/^$categorycode$/} @batch_category_codes ) {
130         $batch_allowed = 1;
131     } else {
132         $barcodes = [];
133     }
134 }
135
136 my ( $template, $loggedinuser, $cookie ) = get_template_and_user (
137     {
138         template_name   => $template_name,
139         query           => $query,
140         type            => "intranet",
141         authnotrequired => 0,
142         flagsrequired   => { circulate => 'circulate_remaining_permissions' },
143     }
144 );
145 my $logged_in_user = Koha::Patrons->find( $loggedinuser );
146
147 my $force_allow_issue = $query->param('forceallow') || 0;
148 if (!C4::Auth::haspermission( C4::Context->userenv->{id} , { circulate => 'force_checkout' } )) {
149     $force_allow_issue = 0;
150 }
151 my $onsite_checkout = $query->param('onsite_checkout');
152
153 if (C4::Context->preference("OnSiteCheckoutAutoCheck") && $onsite_checkout eq "on") {
154     $template->param(onsite_checkout => $onsite_checkout);
155 }
156
157 my @failedrenews = $query->multi_param('failedrenew');    # expected to be itemnumbers
158 our %renew_failed = ();
159 for (@failedrenews) { $renew_failed{$_} = 1; }
160
161 my @failedreturns = $query->multi_param('failedreturn');
162 our %return_failed = ();
163 for (@failedreturns) { $return_failed{$_} = 1; }
164
165 my $searchtype = $query->param('searchtype') || q{contain};
166
167 my $branch = C4::Context->userenv->{'branch'};
168
169 if (C4::Context->preference("DisplayClearScreenButton")) {
170     $template->param(DisplayClearScreenButton => 1);
171 }
172
173 for my $barcode ( @$barcodes ) {
174     $barcode =~ s/^\s*|\s*$//g; # remove leading/trailing whitespace
175     $barcode = barcodedecode($barcode)
176         if( $barcode && C4::Context->preference('itemBarcodeInputFilter'));
177 }
178
179 my $stickyduedate  = $query->param('stickyduedate') || $session->param('stickyduedate');
180 my $duedatespec    = $query->param('duedatespec')   || $session->param('stickyduedate');
181 $duedatespec = eval { output_pref( { dt => dt_from_string( $duedatespec ), dateformat => 'iso', timeformat => '24hr' }); }
182     if ( $duedatespec );
183 my $restoreduedatespec  = $query->param('restoreduedatespec') || $duedatespec || $session->param('stickyduedate');
184 if ( $restoreduedatespec && $restoreduedatespec eq "highholds_empty" ) {
185     undef $restoreduedatespec;
186 }
187 my $issueconfirmed = $query->param('issueconfirmed');
188 my $cancelreserve  = $query->param('cancelreserve');
189 my $print          = $query->param('print') || q{};
190 my $debt_confirmed = $query->param('debt_confirmed') || 0; # Don't show the debt error dialog twice
191 my $charges        = $query->param('charges') || q{};
192
193 # Check if stickyduedate is turned off
194 if ( @$barcodes ) {
195     # was stickyduedate loaded from session?
196     if ( $stickyduedate && ! $query->param("stickyduedate") ) {
197         $session->clear( 'stickyduedate' );
198         $stickyduedate  = $query->param('stickyduedate');
199         $duedatespec    = $query->param('duedatespec');
200     }
201     $session->param('auto_renew', scalar $query->param('auto_renew'));
202 }
203 else {
204     $session->clear('auto_renew');
205 }
206
207 $template->param( auto_renew => $session->param('auto_renew') );
208
209 my ($datedue,$invalidduedate);
210
211 my $duedatespec_allow = C4::Context->preference('SpecifyDueDate');
212 if( $onsite_checkout && !$duedatespec_allow ) {
213     $datedue = output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
214     $datedue .= ' 23:59:00';
215 } elsif( $duedatespec_allow ) {
216     if ( $duedatespec ) {
217         $datedue = eval { dt_from_string( $duedatespec ) };
218         if (! $datedue ) {
219             $invalidduedate = 1;
220             $template->param( IMPOSSIBLE=>1, INVALID_DATE=>$duedatespec );
221         }
222     }
223 }
224
225 # check and see if we should print
226 if ( @$barcodes == 0 && $print eq 'maybe' ) {
227     $print = 'yes';
228 }
229
230 my $inprocess = (@$barcodes == 0) ? '' : $query->param('inprocess');
231 if ( @$barcodes == 0 && $charges eq 'yes' ) {
232     $template->param(
233         PAYCHARGES     => 'yes',
234         borrowernumber => $borrowernumber
235     );
236 }
237
238 if ( $print eq 'yes' && $borrowernumber ne '' ) {
239     if ( C4::Context->boolean_preference('printcirculationslips') ) {
240         my $letter = IssueSlip($branch, $borrowernumber, "QUICK");
241         NetworkPrint($letter->{content});
242     }
243     $query->param( 'borrowernumber', '' );
244     $borrowernumber = '';
245     undef $patron;
246 }
247
248 #
249 # STEP 2 : FIND BORROWER
250 # if there is a list of find borrowers....
251 #
252 my $message;
253 if ($findborrower) {
254     my $patron = Koha::Patrons->find( { cardnumber => $findborrower } );
255     if ( $patron ) {
256         $borrowernumber = $patron->borrowernumber;
257     } else {
258         my $dt_params = { iDisplayLength => -1 };
259         my $results = C4::Utils::DataTables::Members::search(
260             {
261                 searchmember => $findborrower,
262                 searchtype   => $searchtype,
263                 dt_params    => $dt_params,
264             }
265         );
266         my $borrowers = $results->{patrons};
267         if ( scalar @$borrowers == 1 ) {
268             $borrowernumber = $borrowers->[0]->{borrowernumber};
269             $query->param( 'borrowernumber', $borrowernumber );
270             $query->param( 'barcode',           '' );
271         } elsif ( @$borrowers ) {
272             $template->param( borrowers => $borrowers );
273         } else {
274             $query->param( 'findborrower', '' );
275             $message = "'$findborrower'";
276         }
277     }
278 }
279
280 # get the borrower information.....
281 my $balance = 0;
282 $patron ||= Koha::Patrons->find( $borrowernumber ) if $borrowernumber;
283 if ($patron) {
284
285     $template->param( borrowernumber => $patron->borrowernumber );
286     output_and_exit_if_error( $query, $cookie, $template, { module => 'members', logged_in_user => $logged_in_user, current_patron => $patron } );
287
288     my $overdues = $patron->get_overdues;
289     my $issues = $patron->checkouts;
290     $balance = $patron->account->balance;
291
292
293     # if the expiry date is before today ie they have expired
294     if ( $patron->is_expired ) {
295         #borrowercard expired, no issues
296         $template->param(
297             noissues => ($force_allow_issue) ? 0 : "1",
298             forceallow => $force_allow_issue,
299             expired => "1",
300         );
301     }
302     # check for NotifyBorrowerDeparture
303     elsif ( $patron->is_going_to_expire ) {
304         # borrower card soon to expire warn librarian
305         $template->param( "warndeparture" => $patron->dateexpiry ,
306                         );
307         if (C4::Context->preference('ReturnBeforeExpiry')){
308             $template->param("returnbeforeexpiry" => 1);
309         }
310     }
311     $template->param(
312         overduecount => $overdues->count,
313         issuecount   => $issues->count,
314         finetotal    => $balance,
315     );
316
317     if ( $patron and $patron->is_debarred ) {
318         $template->param(
319             'userdebarred'    => $patron->debarred,
320             'debarredcomment' => $patron->debarredcomment,
321         );
322
323         if ( $patron->debarred ne "9999-12-31" ) {
324             $template->param( 'userdebarreddate' => $patron->debarred );
325         }
326     }
327
328     # Calculate and display patron's age
329     if ( !$patron->is_valid_age ) {
330         $template->param( age_limitations => 1 );
331         $template->param( age_low => $patron->category->dateofbirthrequired );
332         $template->param( age_high => $patron->category->upperagelimit );
333     }
334
335 }
336
337 #
338 # STEP 3 : ISSUING
339 #
340 #
341 if (@$barcodes) {
342   my $checkout_infos;
343   for my $barcode ( @$barcodes ) {
344
345     my $template_params = {
346         barcode         => $barcode,
347         onsite_checkout => $onsite_checkout,
348     };
349
350     # always check for blockers on issuing
351     my ( $error, $question, $alerts, $messages ) = CanBookBeIssued(
352         $patron,
353         $barcode, $datedue,
354         $inprocess,
355         undef,
356         {
357             onsite_checkout     => $onsite_checkout,
358             override_high_holds => $override_high_holds || $override_high_holds_tmp || 0,
359         }
360     );
361
362     my $blocker = $invalidduedate ? 1 : 0;
363
364     $template_params->{alert} = $alerts;
365     $template_params->{messages} = $messages;
366
367     my $item = Koha::Items->find({ barcode => $barcode });
368
369     my $biblio;
370     if ( $item ) {
371         $biblio = $item->biblio;
372     }
373
374     # Fix for bug 7494: optional checkout-time fallback search for a book
375
376     if ( $error->{'UNKNOWN_BARCODE'}
377         && C4::Context->preference("itemBarcodeFallbackSearch")
378         && not $batch
379     )
380     {
381      $template_params->{FALLBACK} = 1;
382
383         my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
384         my $query = "kw=" . $barcode;
385         my ( $searcherror, $results, $total_hits ) = $searcher->simple_search_compat($query, 0, 10);
386
387         # if multiple hits, offer options to librarian
388         if ( $total_hits > 0 ) {
389             my @options = ();
390             foreach my $hit ( @{$results} ) {
391                 my $chosen =
392                   TransformMarcToKoha( C4::Search::new_record_from_zebra('biblioserver',$hit) );
393
394                 # offer all barcodes individually
395                 if ( $chosen->{barcode} ) {
396                     foreach my $barcode ( sort split(/\s*\|\s*/, $chosen->{barcode}) ) {
397                         my %chosen_single = %{$chosen};
398                         $chosen_single{barcode} = $barcode;
399                         push( @options, \%chosen_single );
400                     }
401                 }
402             }
403             $template_params->{options} = \@options;
404         }
405     }
406
407     if ( $error->{UNKNOWN_BARCODE} or not $onsite_checkout or not C4::Context->preference("OnSiteCheckoutsForce") ) {
408         delete $question->{'DEBT'} if ($debt_confirmed);
409         foreach my $impossible ( keys %$error ) {
410             $template_params->{$impossible} = $$error{$impossible};
411             $template_params->{IMPOSSIBLE} = 1;
412             $blocker = 1;
413         }
414     }
415
416     if( $item and ( !$blocker or $force_allow_issue ) ){
417         my $confirm_required = 0;
418         unless($issueconfirmed){
419             #  Get the item title for more information
420             my $materials = $item->materials;
421             my $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({ frameworkcode => $biblio->frameworkcode, kohafield => 'items.materials', authorised_value => $materials });
422             $materials = $descriptions->{lib} // $materials;
423             $template_params->{additional_materials} = $materials;
424             $template_params->{itemhomebranch} = $item->homebranch;
425
426             # pass needsconfirmation to template if issuing is possible and user hasn't yet confirmed.
427             foreach my $needsconfirmation ( keys %$question ) {
428                 $template_params->{$needsconfirmation} = $$question{$needsconfirmation};
429                 $template_params->{getTitleMessageIteminfo} = $biblio->title;
430                 $template_params->{getBarcodeMessageIteminfo} = $item->barcode;
431                 $template_params->{NEEDSCONFIRMATION} = 1;
432                 $confirm_required = 1;
433             }
434         }
435         unless($confirm_required) {
436             my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
437             my $issue = AddIssue( $patron->unblessed, $barcode, $datedue, $cancelreserve, undef, undef, { onsite_checkout => $onsite_checkout, auto_renew => $session->param('auto_renew'), switch_onsite_checkout => $switch_onsite_checkout, } );
438             $template_params->{issue} = $issue;
439             $session->clear('auto_renew');
440             $inprocess = 1;
441         }
442     }
443
444     if ($question->{RESERVE_WAITING} or $question->{RESERVED}){
445         $template->param(
446             reserveborrowernumber => $question->{'resborrowernumber'}
447         );
448     }
449
450
451     # FIXME If the issue is confirmed, we launch another time checkouts->count, now display the issue count after issue
452     $patron = Koha::Patrons->find( $borrowernumber );
453     $template_params->{issuecount} = $patron->checkouts->count;
454
455     if ( $item ) {
456         $template_params->{item} = $item;
457         $template_params->{biblio} = $biblio;
458         $template_params->{itembiblionumber} = $biblio->biblionumber;
459     }
460     push @$checkout_infos, $template_params;
461   }
462   unless ( $batch ) {
463     $template->param( %{$checkout_infos->[0]} );
464     $template->param( barcode => $barcodes->[0] );
465   } else {
466     my $confirmation_needed = grep { $_->{NEEDSCONFIRMATION} } @$checkout_infos;
467     $template->param(
468         checkout_infos => $checkout_infos,
469         confirmation_needed => $confirmation_needed,
470     );
471   }
472 }
473
474 ##################################################################################
475 # BUILD HTML
476 # show all reserves of this borrower, and the position of the reservation ....
477 if ($patron) {
478     my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } ); # FIXME must be Koha::Patron->holds
479     my $waiting_holds = $holds->waiting;
480     $template->param(
481         holds_count  => $holds->count(),
482         WaitingHolds => $waiting_holds,
483     );
484 }
485
486 if ( $patron ) {
487     my $noissues;
488     if ( $patron->gonenoaddress ) {
489         $template->param( gna => 1 );
490         $noissues = 1;
491     }
492     if ( $patron->lost ) {
493         $template->param( lost=> 1 );
494         $noissues = 1;
495     }
496     if ( $patron->is_debarred ) {
497         $template->param( dbarred=> 1 );
498         $noissues = 1;
499     }
500     my $account = $patron->account;
501     if( ( my $owing = $account->non_issues_charges ) > 0 ) {
502         my $noissuescharge = C4::Context->preference("noissuescharge") || 5; # FIXME If noissuescharge == 0 then 5, why??
503         $noissues ||= ( not C4::Context->preference("AllowFineOverride") and ( $owing > $noissuescharge ) );
504         $template->param(
505             charges => 1,
506             chargesamount => $owing,
507         )
508     } elsif ( $balance < 0 ) {
509         $template->param(
510             credits => 1,
511             creditsamount => -$balance,
512         );
513     }
514
515     my $no_issues_charge_guarantees = C4::Context->preference("NoIssuesChargeGuarantees");
516     $no_issues_charge_guarantees = undef unless looks_like_number( $no_issues_charge_guarantees );
517     if ( defined $no_issues_charge_guarantees ) {
518         my $guarantees_non_issues_charges = 0;
519         my $guarantees = $patron->guarantee_relationships->guarantees;
520         while ( my $g = $guarantees->next ) {
521             $guarantees_non_issues_charges += $g->account->non_issues_charges;
522         }
523         if ( $guarantees_non_issues_charges > $no_issues_charge_guarantees ) {
524             $template->param(
525                 charges_guarantees    => 1,
526                 chargesamount_guarantees => $guarantees_non_issues_charges,
527             );
528             $noissues = 1 unless C4::Context->preference("allowfineoverride");
529         }
530     }
531
532     if ( $patron->has_overdues ) {
533         $template->param( odues => 1 );
534     }
535
536     if ( $patron->borrowernotes ) {
537         my $borrowernotes = $patron->borrowernotes;
538         $borrowernotes =~ s#\n#<br />#g;
539         $template->param(
540             notes =>1,
541             notesmsg => $borrowernotes,
542         )
543     }
544
545     if ( $noissues ) {
546         $template->param(
547             noissues => ($force_allow_issue) ? 0 : 'true',
548             forceallow => $force_allow_issue,
549         );
550     }
551 }
552
553 my $messages = Koha::Patron::Messages->search(
554     {
555         'me.borrowernumber' => $borrowernumber,
556     },
557     {
558        join => 'manager',
559        '+select' => ['manager.surname', 'manager.firstname' ],
560        '+as' => ['manager_surname', 'manager_firstname'],
561     }
562 );
563
564 my $fast_cataloging = 0;
565 if ( Koha::BiblioFrameworks->find('FA') ) {
566     $fast_cataloging = 1 
567 }
568
569 my $view = $batch
570     ?'batch_checkout_view'
571     : 'circview';
572
573 my @relatives;
574 if ( $patron ) {
575     if ( my @guarantors = $patron->guarantor_relationships()->guarantors() ) {
576         push( @relatives, $_->id ) for @guarantors;
577         push( @relatives, $_->id ) for $patron->siblings();
578     } else {
579         push( @relatives, $_->id ) for $patron->guarantee_relationships()->guarantees();
580     }
581 }
582 my $relatives_issues_count =
583   Koha::Database->new()->schema()->resultset('Issue')
584   ->count( { borrowernumber => \@relatives } );
585
586 if ( $patron ) {
587     my $av = Koha::AuthorisedValues->search({ category => 'ROADTYPE', authorised_value => $patron->streettype });
588     my $roadtype = $av->count ? $av->next->lib : '';
589     $template->param(
590         roadtype          => $roadtype,
591         patron            => $patron,
592         categoryname      => $patron->category->description,
593         expiry            => $patron->dateexpiry,
594     );
595 }
596
597 # Restore date if changed by holds and/or save stickyduedate to session
598 if ($restoreduedatespec || $stickyduedate) {
599     $duedatespec = $restoreduedatespec || $duedatespec;
600
601     if ($stickyduedate) {
602         $session->param( 'stickyduedate', $duedatespec );
603     }
604 } elsif (defined($duedatespec) && !defined($restoreduedatespec)) {
605     undef $duedatespec;
606 }
607
608 $template->param(
609     messages           => $messages,
610     borrowernumber    => $borrowernumber,
611     branch            => $branch,
612     was_renewed       => scalar $query->param('was_renewed') ? 1 : 0,
613     barcodes          => $barcodes,
614     stickyduedate     => $stickyduedate,
615     duedatespec       => $duedatespec,
616     restoreduedatespec => $restoreduedatespec,
617     message           => $message,
618     totaldue          => sprintf('%.2f', $balance), # FIXME not used in template?
619     inprocess         => $inprocess,
620     $view             => 1,
621     batch_allowed     => $batch_allowed,
622     batch             => $batch,
623     AudioAlerts           => C4::Context->preference("AudioAlerts"),
624     fast_cataloging   => $fast_cataloging,
625     CircAutoPrintQuickSlip   => C4::Context->preference("CircAutoPrintQuickSlip"),
626     RoutingSerials => C4::Context->preference('RoutingSerials'),
627     relatives_issues_count => $relatives_issues_count,
628     relatives_borrowernumbers => \@relatives,
629 );
630
631
632 if ( C4::Context->preference("ExportCircHistory") ) {
633     $template->param(csv_profiles => [ Koha::CsvProfiles->search({ type => 'marc' }) ]);
634 }
635
636 my $has_modifications = Koha::Patron::Modifications->search( { borrowernumber => $borrowernumber } )->count;
637 $template->param(
638     debt_confirmed            => $debt_confirmed,
639     SpecifyDueDate            => $duedatespec_allow,
640     PatronAutoComplete      => C4::Context->preference("PatronAutoComplete"),
641     debarments                => scalar GetDebarments({ borrowernumber => $borrowernumber }),
642     todaysdate                => output_pref( { dt => dt_from_string()->set(hour => 23)->set(minute => 59), dateformat => 'sql' } ),
643     has_modifications         => $has_modifications,
644     override_high_holds       => $override_high_holds,
645     nopermission              => scalar $query->param('nopermission'),
646     autoswitched              => $autoswitched,
647     logged_in_user            => $logged_in_user,
648 );
649
650 output_html_with_http_headers $query, $cookie, $template->output;