Bug 16166: Improve L1 cache performance and safety
[koha.git] / catalogue / detail.pl
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
19 use Modern::Perl;
20
21 use CGI qw ( -utf8 );
22 use C4::Acquisition qw( GetHistory );
23 use C4::Auth;
24 use C4::Koha;
25 use C4::Serials;    #uses getsubscriptionfrom biblionumber
26 use C4::Output;
27 use C4::Biblio;
28 use C4::Items;
29 use C4::Circulation;
30 use C4::Branch;
31 use C4::Reserves;
32 use C4::Members; # to use GetMember
33 use C4::Serials;
34 use C4::XISBN qw(get_xisbns get_biblionumber_from_isbn);
35 use C4::External::Amazon;
36 use C4::Search;         # enabled_staff_search_views
37 use C4::Tags qw(get_tags);
38 use C4::XSLT;
39 use C4::Images;
40 use Koha::DateUtils;
41 use C4::HTML5Media;
42 use C4::CourseReserves qw(GetItemCourseReservesInfo);
43 use C4::Acquisition qw(GetOrdersByBiblionumber);
44 use Koha::Virtualshelves;
45
46 my $query = CGI->new();
47
48 my $analyze = $query->param('analyze');
49
50 my ( $template, $borrowernumber, $cookie, $flags ) = get_template_and_user(
51     {
52     template_name   =>  'catalogue/detail.tt',
53         query           => $query,
54         type            => "intranet",
55         authnotrequired => 0,
56         flagsrequired   => { catalogue => 1 },
57     }
58 );
59
60 my $biblionumber = $query->param('biblionumber');
61 my $record       = GetMarcBiblio($biblionumber);
62
63 if ( not defined $record ) {
64     # biblionumber invalid -> report and exit
65     $template->param( unknownbiblionumber => 1,
66                       biblionumber => $biblionumber );
67     output_html_with_http_headers $query, $cookie, $template->output;
68     exit;
69 }
70
71 if($query->cookie("holdfor")){ 
72     my $holdfor_patron = GetMember('borrowernumber' => $query->cookie("holdfor"));
73     $template->param(
74         holdfor => $query->cookie("holdfor"),
75         holdfor_surname => $holdfor_patron->{'surname'},
76         holdfor_firstname => $holdfor_patron->{'firstname'},
77         holdfor_cardnumber => $holdfor_patron->{'cardnumber'},
78     );
79 }
80
81 my $fw           = GetFrameworkCode($biblionumber);
82 my $showallitems = $query->param('showallitems');
83 my $marcflavour  = C4::Context->preference("marcflavour");
84
85 # XSLT processing of some stuff
86 my $xslfile = C4::Context->preference('XSLTDetailsDisplay');
87 my $lang   = $xslfile ? C4::Languages::getlanguage()  : undef;
88 my $sysxml = $xslfile ? C4::XSLT::get_xslt_sysprefs() : undef;
89
90 if ( $xslfile ) {
91     $template->param(
92         XSLTDetailsDisplay => '1',
93         XSLTBloc => XSLTParse4Display(
94                         $biblionumber, $record, "XSLTDetailsDisplay",
95                         1, undef, $sysxml, $xslfile, $lang
96                     )
97     );
98 }
99
100 $template->param( 'SpineLabelShowPrintOnBibDetails' => C4::Context->preference("SpineLabelShowPrintOnBibDetails") );
101 $template->param( ocoins => GetCOinSBiblio($record) );
102
103 # some useful variables for enhanced content;
104 # in each case, we're grabbing the first value we find in
105 # the record and normalizing it
106 my $upc = GetNormalizedUPC($record,$marcflavour);
107 my $ean = GetNormalizedEAN($record,$marcflavour);
108 my $oclc = GetNormalizedOCLCNumber($record,$marcflavour);
109 my $isbn = GetNormalizedISBN(undef,$record,$marcflavour);
110
111 $template->param(
112     normalized_upc => $upc,
113     normalized_ean => $ean,
114     normalized_oclc => $oclc,
115     normalized_isbn => $isbn,
116 );
117
118 my $marcnotesarray   = GetMarcNotes( $record, $marcflavour );
119 my $marcisbnsarray   = GetMarcISBN( $record, $marcflavour );
120 my $marcauthorsarray = GetMarcAuthors( $record, $marcflavour );
121 my $marcsubjctsarray = GetMarcSubjects( $record, $marcflavour );
122 my $marcseriesarray  = GetMarcSeries($record,$marcflavour);
123 my $marcurlsarray    = GetMarcUrls    ($record,$marcflavour);
124 my $marchostsarray  = GetMarcHosts($record,$marcflavour);
125 my $subtitle         = GetRecordValue('subtitle', $record, $fw);
126
127 # Get Branches, Itemtypes and Locations
128 my $branches = GetBranches();
129 my $itemtypes = GetItemTypes();
130 my $dbh = C4::Context->dbh;
131
132 my @all_items = GetItemsInfo( $biblionumber );
133 my @items;
134 for my $itm (@all_items) {
135     push @items, $itm unless ( $itm->{itemlost} && GetHideLostItemsPreference($borrowernumber) && !$showallitems);
136 }
137
138 # flag indicating existence of at least one item linked via a host record
139 my $hostrecords;
140 # adding items linked via host biblios
141 my @hostitems = GetHostItemsInfo($record);
142 if (@hostitems){
143         $hostrecords =1;
144         push (@items,@hostitems);
145 }
146
147 my $dat = &GetBiblioData($biblionumber);
148
149 #coping with subscriptions
150 my $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber);
151 my @subscriptions       = SearchSubscriptions({ biblionumber => $biblionumber, orderby => 'title' });
152 my @subs;
153
154 foreach my $subscription (@subscriptions) {
155     my %cell;
156         my $serials_to_display;
157     $cell{subscriptionid}    = $subscription->{subscriptionid};
158     $cell{subscriptionnotes} = $subscription->{internalnotes};
159     $cell{missinglist}       = $subscription->{missinglist};
160     $cell{librariannote}     = $subscription->{librariannote};
161     $cell{branchcode}        = $subscription->{branchcode};
162     $cell{branchname}        = GetBranchName($subscription->{branchcode});
163     $cell{hasalert}          = $subscription->{hasalert};
164     $cell{callnumber}        = $subscription->{callnumber};
165     $cell{closed}            = $subscription->{closed};
166     #get the three latest serials.
167         $serials_to_display = $subscription->{staffdisplaycount};
168         $serials_to_display = C4::Context->preference('StaffSerialIssueDisplayCount') unless $serials_to_display;
169         $cell{staffdisplaycount} = $serials_to_display;
170     $cell{latestserials} =
171       GetLatestSerials( $subscription->{subscriptionid}, $serials_to_display );
172     push @subs, \%cell;
173 }
174
175
176 # Get acquisition details
177 if ( C4::Context->preference('AcquisitionDetails') ) {
178     my $orders = C4::Acquisition::GetHistory( biblionumber => $biblionumber, get_canceled_order => 1 );
179     $template->param(
180         orders => $orders,
181     );
182 }
183
184 if ( defined $dat->{'itemtype'} ) {
185     $dat->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtypes->{ $dat->{itemtype} }{imageurl} );
186 }
187
188 $dat->{'count'} = scalar @all_items + @hostitems;
189 $dat->{'showncount'} = scalar @items + @hostitems;
190 $dat->{'hiddencount'} = scalar @all_items + @hostitems - scalar @items;
191
192 my $shelflocations = GetKohaAuthorisedValues('items.location', $fw);
193 my $collections    = GetKohaAuthorisedValues('items.ccode'   , $fw);
194 my $copynumbers    = GetKohaAuthorisedValues('items.copynumber', $fw);
195 my (@itemloop, @otheritemloop, %itemfields);
196 my $norequests = 1;
197
198 if ( my $lost_av = GetAuthValCode('items.itemlost', $fw) ) {
199     $template->param( itemlostloop => GetAuthorisedValues( $lost_av ) );
200 }
201 if ( my $damaged_av = GetAuthValCode('items.damaged', $fw) ) {
202     $template->param( itemdamagedloop => GetAuthorisedValues( $damaged_av ) );
203 }
204
205 my $materials_authvalcode = GetAuthValCode('items.materials', $fw);
206 my %materials_map;
207 if ($materials_authvalcode) {
208     my $materials_authvals = GetAuthorisedValues($materials_authvalcode);
209     if ($materials_authvals) {
210         foreach my $value (@$materials_authvals) {
211             $materials_map{$value->{authorised_value}} = $value->{lib};
212         }
213     }
214 }
215
216 my $analytics_flag;
217 my $materials_flag; # set this if the items have anything in the materials field
218 my $currentbranch = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
219 if ($currentbranch and C4::Context->preference('SeparateHoldings')) {
220     $template->param(SeparateHoldings => 1);
221 }
222 my $separatebranch = C4::Context->preference('SeparateHoldingsBranch') || 'homebranch';
223 foreach my $item (@items) {
224     my $itembranchcode = $item->{$separatebranch};
225
226     # can place holds defaults to yes
227     $norequests = 0 unless ( ( $item->{'notforloan'} > 0 ) || ( $item->{'itemnotforloan'} > 0 ) );
228
229     $item->{imageurl} = defined $item->{itype} ? getitemtypeimagelocation('intranet', $itemtypes->{ $item->{itype} }{imageurl})
230                                                : '';
231
232     $item->{datedue} = format_sqldatetime($item->{datedue});
233
234     #get shelf location and collection code description if they are authorised value.
235     # same thing for copy number
236     my $shelfcode = $item->{'location'};
237     $item->{'location'} = $shelflocations->{$shelfcode} if ( defined( $shelfcode ) && defined($shelflocations) && exists( $shelflocations->{$shelfcode} ) );
238     my $ccode = $item->{'ccode'};
239     $item->{'ccode'} = $collections->{$ccode} if ( defined( $ccode ) && defined($collections) && exists( $collections->{$ccode} ) );
240     my $copynumber = $item->{'copynumber'};
241     $item->{'copynumber'} = $copynumbers->{$copynumber} if ( defined($copynumber) && defined($copynumbers) && exists( $copynumbers->{$copynumber} ) );
242     foreach (qw(ccode enumchron copynumber stocknumber itemnotes itemnotes_nonpublic uri)) {
243         $itemfields{$_} = 1 if ( $item->{$_} );
244     }
245
246     # checking for holds
247     my ($reservedate,$reservedfor,$expectedAt,undef,$wait) = GetReservesFromItemnumber($item->{itemnumber});
248     my $ItemBorrowerReserveInfo = C4::Members::GetMember( borrowernumber => $reservedfor);
249     
250     if (C4::Context->preference('HidePatronName')){
251         $item->{'hidepatronname'} = 1;
252     }
253
254     if ( defined $reservedate ) {
255         $item->{backgroundcolor} = 'reserved';
256         $item->{reservedate}     = $reservedate;
257         $item->{ReservedForBorrowernumber}     = $reservedfor;
258         $item->{ReservedForSurname}     = $ItemBorrowerReserveInfo->{'surname'};
259         $item->{ReservedForFirstname}   = $ItemBorrowerReserveInfo->{'firstname'};
260         $item->{ExpectedAtLibrary}      = $branches->{$expectedAt}{branchname};
261         $item->{Reservedcardnumber}             = $ItemBorrowerReserveInfo->{'cardnumber'};
262         # Check waiting status
263         $item->{waitingdate} = $wait;
264     }
265
266
267         # Check the transit status
268     my ( $transfertwhen, $transfertfrom, $transfertto ) = GetTransfers($item->{itemnumber});
269     if ( defined( $transfertwhen ) && ( $transfertwhen ne '' ) ) {
270         $item->{transfertwhen} = $transfertwhen;
271         $item->{transfertfrom} = $branches->{$transfertfrom}{branchname};
272         $item->{transfertto}   = $branches->{$transfertto}{branchname};
273         $item->{nocancel} = 1;
274     }
275
276     foreach my $f (qw( itemnotes )) {
277         if ($item->{$f}) {
278             $item->{$f} =~ s|\n|<br />|g;
279             $itemfields{$f} = 1;
280         }
281     }
282
283     #item has a host number if its biblio number does not match the current bib
284
285     if ($item->{biblionumber} ne $biblionumber){
286         $item->{hostbiblionumber} = $item->{biblionumber};
287         $item->{hosttitle} = GetBiblioData($item->{biblionumber})->{title};
288     }
289         
290     #count if item is used in analytical bibliorecords
291     my $countanalytics= GetAnalyticsCount($item->{itemnumber});
292     if ($countanalytics > 0){
293         $analytics_flag=1;
294         $item->{countanalytics} = $countanalytics;
295     }
296
297     if (defined($item->{'materials'}) && $item->{'materials'} =~ /\S/){
298         $materials_flag = 1;
299         if (defined $materials_map{ $item->{materials} }) {
300             $item->{materials} = $materials_map{ $item->{materials} };
301         }
302     }
303
304     if ( C4::Context->preference('UseCourseReserves') ) {
305         $item->{'course_reserves'} = GetItemCourseReservesInfo( itemnumber => $item->{'itemnumber'} );
306     }
307
308     if ( C4::Context->preference('IndependentBranches') ) {
309         my $userenv = C4::Context->userenv();
310         if ( not C4::Context->IsSuperLibrarian()
311             and $userenv->{branch} ne $item->{homebranch} ) {
312             $item->{cannot_be_edited} = 1;
313         }
314     }
315
316     if ($currentbranch and $currentbranch ne "NO_LIBRARY_SET"
317     and C4::Context->preference('SeparateHoldings')) {
318         if ($itembranchcode and $itembranchcode eq $currentbranch) {
319             push @itemloop, $item;
320         } else {
321             push @otheritemloop, $item;
322         }
323     } else {
324         push @itemloop, $item;
325     }
326 }
327
328 # Display only one tab if one items list is empty
329 if (scalar(@itemloop) == 0 || scalar(@otheritemloop) == 0) {
330     $template->param(SeparateHoldings => 0);
331     if (scalar(@itemloop) == 0) {
332         @itemloop = @otheritemloop;
333     }
334 }
335
336 $template->param( norequests => $norequests );
337 $template->param(
338         MARCNOTES   => $marcnotesarray,
339         MARCSUBJCTS => $marcsubjctsarray,
340         MARCAUTHORS => $marcauthorsarray,
341         MARCSERIES  => $marcseriesarray,
342         MARCURLS => $marcurlsarray,
343     MARCISBNS => $marcisbnsarray,
344         MARCHOSTS => $marchostsarray,
345         subtitle    => $subtitle,
346         itemdata_ccode      => $itemfields{ccode},
347         itemdata_enumchron  => $itemfields{enumchron},
348         itemdata_uri        => $itemfields{uri},
349         itemdata_copynumber => $itemfields{copynumber},
350         itemdata_stocknumber => $itemfields{stocknumber},
351         volinfo                         => $itemfields{enumchron},
352         itemdata_itemnotes  => $itemfields{itemnotes},
353         itemdata_nonpublicnotes => $itemfields{itemnotes_nonpublic},
354         z3950_search_params     => C4::Search::z3950_search_args($dat),
355         hostrecords         => $hostrecords,
356         analytics_flag  => $analytics_flag,
357         C4::Search::enabled_staff_search_views,
358         materials       => $materials_flag,
359 );
360
361 if (C4::Context->preference("AlternateHoldingsField") && scalar @items == 0) {
362     my $fieldspec = C4::Context->preference("AlternateHoldingsField");
363     my $subfields = substr $fieldspec, 3;
364     my $holdingsep = C4::Context->preference("AlternateHoldingsSeparator") || ' ';
365     my @alternateholdingsinfo = ();
366     my @holdingsfields = $record->field(substr $fieldspec, 0, 3);
367
368     for my $field (@holdingsfields) {
369         my %holding = ( holding => '' );
370         my $havesubfield = 0;
371         for my $subfield ($field->subfields()) {
372             if ((index $subfields, $$subfield[0]) >= 0) {
373                 $holding{'holding'} .= $holdingsep if (length $holding{'holding'} > 0);
374                 $holding{'holding'} .= $$subfield[1];
375                 $havesubfield++;
376             }
377         }
378         if ($havesubfield) {
379             push(@alternateholdingsinfo, \%holding);
380         }
381     }
382
383     $template->param(
384         ALTERNATEHOLDINGS   => \@alternateholdingsinfo,
385         );
386 }
387
388 my @results = ( $dat, );
389 foreach ( keys %{$dat} ) {
390     $template->param( "$_" => defined $dat->{$_} ? $dat->{$_} : '' );
391 }
392
393 # does not work: my %views_enabled = map { $_ => 1 } $template->query(loop => 'EnableViews');
394 # method query not found?!?!
395 $template->param( AmazonTld => get_amazon_tld() ) if ( C4::Context->preference("AmazonCoverImages"));
396 $template->param(
397     itemloop        => \@itemloop,
398     otheritemloop   => \@otheritemloop,
399     biblionumber        => $biblionumber,
400     ($analyze? 'analyze':'detailview') =>1,
401     subscriptions       => \@subs,
402     subscriptionsnumber => $subscriptionsnumber,
403     subscriptiontitle   => $dat->{title},
404     searchid            => scalar $query->param('searchid'),
405 );
406
407 # $debug and $template->param(debug_display => 1);
408
409 # Lists
410
411 if (C4::Context->preference("virtualshelves") ) {
412     my $shelves = Koha::Virtualshelves->search(
413         {
414             biblionumber => $biblionumber,
415             category => 2,
416         },
417         {
418             join => 'virtualshelfcontents',
419         }
420     );
421     $template->param( 'shelves' => $shelves );
422 }
423
424 # XISBN Stuff
425 if (C4::Context->preference("FRBRizeEditions")==1) {
426     eval {
427         $template->param(
428             XISBNS => get_xisbns($isbn)
429         );
430     };
431     if ($@) { warn "XISBN Failed $@"; }
432 }
433
434 if ( C4::Context->preference("LocalCoverImages") == 1 ) {
435     my @images = ListImagesForBiblio($biblionumber);
436     $template->{VARS}->{localimages} = \@images;
437 }
438
439 # HTML5 Media
440 if ( (C4::Context->preference("HTML5MediaEnabled") eq 'both') or (C4::Context->preference("HTML5MediaEnabled") eq 'staff') ) {
441     $template->param( C4::HTML5Media->gethtml5media($record));
442 }
443
444 # Displaying tags
445
446 my $tag_quantity;
447 if (C4::Context->preference('TagsEnabled') and $tag_quantity = C4::Context->preference('TagsShowOnDetail')) {
448     $template->param(
449         TagsEnabled => 1,
450         TagsShowOnDetail => $tag_quantity
451     );
452     $template->param(TagLoop => get_tags({biblionumber=>$biblionumber, approved=>1,
453                                 'sort'=>'-weight', limit=>$tag_quantity}));
454 }
455
456 #we only need to pass the number of holds to the template
457 my $holds = C4::Reserves::GetReservesFromBiblionumber({ biblionumber => $biblionumber, all_dates => 1 });
458 $template->param( holdcount => scalar ( @$holds ) );
459
460 my $StaffDetailItemSelection = C4::Context->preference('StaffDetailItemSelection');
461 if ($StaffDetailItemSelection) {
462     # Only enable item selection if user can execute at least one action
463     if (
464         $flags->{superlibrarian}
465         || (
466             ref $flags->{tools} eq 'HASH' && (
467                 $flags->{tools}->{items_batchmod}       # Modify selected items
468                 || $flags->{tools}->{items_batchdel}    # Delete selected items
469             )
470         )
471         || ( ref $flags->{tools} eq '' && $flags->{tools} )
472       )
473     {
474         $template->param(
475             StaffDetailItemSelection => $StaffDetailItemSelection );
476     }
477 }
478
479 my @allorders_using_biblio = GetOrdersByBiblionumber ($biblionumber);
480 my @deletedorders_using_biblio;
481 my @orders_using_biblio;
482 my @baskets_orders;
483 my @baskets_deletedorders;
484
485 foreach my $myorder (@allorders_using_biblio) {
486     my $basket = $myorder->{'basketno'};
487     if ((defined $myorder->{'datecancellationprinted'}) and  ($myorder->{'datecancellationprinted'} ne '0000-00-00') ){
488         push @deletedorders_using_biblio, $myorder;
489         unless (grep(/^$basket$/, @baskets_deletedorders)){
490             push @baskets_deletedorders,$myorder->{'basketno'};
491         }
492     }
493     else {
494         push @orders_using_biblio, $myorder;
495         unless (grep(/^$basket$/, @baskets_orders)){
496             push @baskets_orders,$myorder->{'basketno'};
497             }
498     }
499 }
500
501 my $count_orders_using_biblio = scalar @orders_using_biblio ;
502 $template->param (countorders => $count_orders_using_biblio);
503
504 my $count_deletedorders_using_biblio = scalar @deletedorders_using_biblio ;
505 $template->param (countdeletedorders => $count_deletedorders_using_biblio);
506
507 $template->param (basketsorders => \@baskets_orders);
508 $template->param (basketsdeletedorders => \@baskets_deletedorders);
509
510 output_html_with_http_headers $query, $cookie, $template->output;