Bug 33568: Do not embed library names
[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 HTML::Entities;
23 use C4::Auth qw( get_template_and_user );
24 use C4::Context;
25 use C4::Koha qw(
26     GetAuthorisedValues
27     getitemtypeimagelocation
28     GetNormalizedEAN
29     GetNormalizedISBN
30     GetNormalizedOCLCNumber
31     GetNormalizedUPC
32 );
33 use C4::Serials qw( CountSubscriptionFromBiblionumber SearchSubscriptions GetLatestSerials );
34 use C4::Output qw( output_html_with_http_headers );
35 use C4::Biblio qw( GetBiblioData GetFrameworkCode );
36 use C4::Items qw( GetAnalyticsCount );
37 use C4::Reserves;
38 use C4::Serials qw( CountSubscriptionFromBiblionumber SearchSubscriptions GetLatestSerials );
39 use C4::XISBN qw( get_xisbns );
40 use C4::External::Amazon qw( get_amazon_tld );
41 use C4::Search qw( z3950_search_args enabled_staff_search_views new_record_from_zebra );
42 use C4::Tags qw( get_tags );
43 use C4::XSLT qw( XSLTParse4Display );
44 use Koha::DateUtils qw( format_sqldatetime );
45 use C4::HTML5Media;
46 use C4::CourseReserves qw( GetItemCourseReservesInfo );
47 use Koha::AuthorisedValues;
48 use Koha::Biblios;
49 use Koha::Biblio::ItemGroup::Items;
50 use Koha::Biblio::ItemGroups;
51 use Koha::CoverImages;
52 use Koha::DateUtils;
53 use Koha::Illrequests;
54 use Koha::Items;
55 use Koha::ItemTypes;
56 use Koha::Patrons;
57 use Koha::Virtualshelves;
58 use Koha::Plugins;
59 use Koha::Recalls;
60 use Koha::SearchEngine::Search;
61 use Koha::SearchEngine::QueryBuilder;
62 use Koha::Serial::Items;
63
64 my $query = CGI->new();
65
66 my $analyze = $query->param('analyze');
67
68 my ( $template, $borrowernumber, $cookie, $flags ) = get_template_and_user(
69     {
70     template_name   =>  'catalogue/detail.tt',
71         query           => $query,
72         type            => "intranet",
73         flagsrequired   => { catalogue => 1 },
74     }
75 );
76
77 # Determine if we should be offering any enhancement plugin buttons
78 if ( C4::Context->config('enable_plugins') ) {
79     # Only pass plugins that can offer a toolbar button
80     my @plugins = Koha::Plugins->new()->GetPlugins({
81         method => 'intranet_catalog_biblio_enhancements_toolbar_button'
82     });
83     $template->param(
84         plugins => \@plugins,
85     );
86 }
87
88 my $biblionumber = $query->param('biblionumber');
89 $biblionumber = HTML::Entities::encode($biblionumber);
90 my $biblio = Koha::Biblios->find( $biblionumber );
91 $template->param( 'biblio', $biblio );
92
93 unless ( $biblio ) {
94     # biblionumber invalid -> report and exit
95     $template->param( unknownbiblionumber => 1,
96                       biblionumber => $biblionumber );
97     output_html_with_http_headers $query, $cookie, $template->output;
98     exit;
99 }
100
101 my $marc_record = eval { $biblio->metadata->record };
102 my $invalid_marc_record = $@ || !$marc_record;
103 if ($invalid_marc_record) {
104     $template->param( decoding_error => $@ );
105     my $marc_xml = C4::Charset::StripNonXmlChars( $biblio->metadata->metadata );
106
107     $marc_record = eval {
108         MARC::Record::new_from_xml( $marc_xml, 'UTF-8',
109             C4::Context->preference('marcflavour') );
110     };
111 }
112
113 my $op = $query->param('op') || q{};
114 if ( $op eq 'set_item_group' ) {
115     my $item_group_id = $query->param('item_group_id');
116     my @itemnumbers   = $query->multi_param('itemnumber');
117
118     foreach my $item_id (@itemnumbers) {
119         my $item_group_item = Koha::Biblio::ItemGroup::Items->find( { item_id => $item_id } );
120
121         if ($item_group_item) {
122             $item_group_item->item_group_id($item_group_id);
123         }
124         else {
125             $item_group_item = Koha::Biblio::ItemGroup::Item->new(
126                 {
127                     item_id        => $item_id,
128                     item_group_id  => $item_group_id,
129                 }
130             );
131         }
132
133         $item_group_item->store();
134     }
135 }
136 elsif ( $op eq 'unset_item_group' ) {
137     my $item_group_id   = $query->param('item_group_id');
138     my @itemnumbers = $query->multi_param('itemnumber');
139
140     foreach my $item_id (@itemnumbers) {
141         my $item_group_item = Koha::Biblio::ItemGroup::Items->find( { item_id => $item_id } );
142         $item_group_item->delete() if $item_group_item;
143     }
144 }
145
146 if($query->cookie("holdfor")){
147     my $holdfor_patron = Koha::Patrons->find( $query->cookie("holdfor") );
148     if ( $holdfor_patron ) {
149         $template->param(
150             holdfor        => $query->cookie("holdfor"),
151             holdfor_patron => $holdfor_patron,
152         );
153     }
154 }
155
156 if($query->cookie("searchToOrder")){
157     my ( $basketno, $vendorid ) = split( /\//, $query->cookie("searchToOrder") );
158     $template->param(
159         searchtoorder_basketno => $basketno,
160         searchtoorder_vendorid => $vendorid
161     );
162 }
163
164 my $fw           = GetFrameworkCode($biblionumber);
165 my $showallitems = $query->param('showallitems');
166 my $marcflavour  = C4::Context->preference("marcflavour");
167
168 $template->param( 'SpineLabelShowPrintOnBibDetails' => C4::Context->preference("SpineLabelShowPrintOnBibDetails") );
169
170 $template->param( ocoins => !$invalid_marc_record ? $biblio->get_coins : undef );
171
172 # some useful variables for enhanced content;
173 # in each case, we're grabbing the first value we find in
174 # the record and normalizing it
175 my $upc = GetNormalizedUPC($marc_record,$marcflavour);
176 my $ean = GetNormalizedEAN($marc_record,$marcflavour);
177 my $oclc = GetNormalizedOCLCNumber($marc_record,$marcflavour);
178 my $isbn = GetNormalizedISBN(undef,$marc_record,$marcflavour);
179 my $content_identifier_exists;
180 if ( $isbn or $ean or $oclc or $upc ) {
181     $content_identifier_exists = 1;
182 }
183
184 $template->param(
185     normalized_upc => $upc,
186     normalized_ean => $ean,
187     normalized_oclc => $oclc,
188     normalized_isbn => $isbn,
189     content_identifier_exists =>  $content_identifier_exists,
190 );
191
192 my $itemtypes = { map { $_->itemtype => $_ } @{ Koha::ItemTypes->search_with_localization->as_list } };
193 my $patron = Koha::Patrons->find( $borrowernumber );
194 my $include_lost_items = !$patron->category->hidelostitems || $showallitems;
195 my $items_params = {
196     ( $invalid_marc_record ? () : ( host_items => 1 ) ),
197 };
198 my $all_items = $biblio->items($items_params);
199 my $items_to_display = $all_items->search({ $include_lost_items ? () : ( itemlost => 0 ) });
200
201 my $dat = &GetBiblioData($biblionumber);
202
203 #is biblio a collection and are bundles enabled
204 my $leader = $marc_record->leader();
205 $dat->{bundlesEnabled} = ( ( substr( $leader, 7, 1 ) eq 'c' )
206       && C4::Context->preference('BundleNotLoanValue') ) ? 1 : 0;
207
208 #coping with subscriptions
209 my $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber);
210 my @subscriptions       = SearchSubscriptions({ biblionumber => $biblionumber, orderby => 'title' });
211 my @subs;
212
213 foreach my $subscription (@subscriptions) {
214     my %cell;
215     my $serials_to_display;
216     $cell{subscriptionid}    = $subscription->{subscriptionid};
217     $cell{subscriptionnotes} = $subscription->{internalnotes};
218     $cell{missinglist}       = $subscription->{missinglist};
219     $cell{librariannote}     = $subscription->{librariannote};
220     $cell{branchcode}        = $subscription->{branchcode};
221     $cell{hasalert}          = $subscription->{hasalert};
222     $cell{callnumber}        = $subscription->{callnumber};
223     $cell{location}          = $subscription->{location};
224     $cell{closed}            = $subscription->{closed};
225     #get the three latest serials.
226     $serials_to_display = $subscription->{staffdisplaycount};
227     $serials_to_display = C4::Context->preference('StaffSerialIssueDisplayCount') unless $serials_to_display;
228     $cell{staffdisplaycount} = $serials_to_display;
229     $cell{latestserials} =
230       GetLatestSerials( $subscription->{subscriptionid}, $serials_to_display );
231     push @subs, \%cell;
232 }
233
234 # Get component parts details
235 my $showcomp = C4::Context->preference('ShowComponentRecords');
236 my $show_analytics;
237 if ( $showcomp eq 'both' || $showcomp eq 'staff' ) {
238     if ( my $components = !$invalid_marc_record ? $biblio->get_marc_components(C4::Context->preference('MaxComponentRecords')) : undef ) {
239         $show_analytics = 1 if @{$components}; # just show link when having results
240         $template->param( analytics_error => 1 ) if grep { $_->message eq 'component_search' } @{$biblio->object_messages};
241         my $parts;
242         for my $part ( @{$components} ) {
243             $part = C4::Search::new_record_from_zebra( 'biblioserver', $part );
244             my $id = Koha::SearchEngine::Search::extract_biblionumber( $part );
245
246             push @{$parts},
247               XSLTParse4Display(
248                 {
249                     biblionumber => $id,
250                     record       => $part,
251                     xsl_syspref  => "XSLTResultsDisplay",
252                     fix_amps     => 1,
253                 }
254               );
255         }
256         $template->param( ComponentParts => $parts );
257         my ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
258         my $cpq = $comp_query_str . "&sort_by=" . $comp_sort;
259         $template->param( ComponentPartsQuery => $cpq );
260     }
261 } else { # check if we should show analytics anyway
262     $show_analytics = 1 if !$invalid_marc_record && @{$biblio->get_marc_components(1)}; # count matters here, results does not
263     $template->param( analytics_error => 1 ) if grep { $_->message eq 'component_search' } @{$biblio->object_messages};
264 }
265
266 # Display volumes link
267 my $show_volumes = ( !$invalid_marc_record && @{ $biblio->get_marc_volumes(1) } ) ? 1 : 0;
268
269 # XSLT processing of some stuff
270 my $xslt_variables = {
271     show_analytics_link => $show_analytics,
272     show_volumes_link   => $show_volumes
273 };
274 $template->param(
275     XSLTDetailsDisplay => '1',
276     XSLTBloc => XSLTParse4Display({
277         biblionumber   => $biblionumber,
278         record         => $marc_record,
279         xsl_syspref    => "XSLTDetailsDisplay",
280         fix_amps       => 1,
281         xslt_variables => $xslt_variables,
282     }),
283 );
284
285 # Get acquisition details
286 if ( C4::Context->preference('AcquisitionDetails') ) {
287     my $orders = Koha::Acquisition::Orders->search(
288         { biblionumber => $biblionumber },
289         {
290             join => 'basketno',
291             order_by => 'basketno.booksellerid'
292         }
293     );    # GetHistory sorted by aqbooksellerid, but does it make sense?
294
295     $template->param(
296         orders => $orders,
297     );
298 }
299
300 if ( C4::Context->preference('suggestion') ) {
301     my $suggestions = Koha::Suggestions->search(
302         {
303             biblionumber => $biblionumber,
304             archived     => 0,
305         },
306         {
307             order_by => { -desc => 'suggesteddate' }
308         }
309     );
310     my $nb_archived_suggestions = Koha::Suggestions->search({ biblionumber => $biblionumber, archived => 1 })->count;
311     $template->param( suggestions => $suggestions, nb_archived_suggestions => $nb_archived_suggestions );
312 }
313
314 if ( defined $dat->{'itemtype'} ) {
315     $dat->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtypes->{ $dat->{itemtype} }->imageurl );
316 }
317
318 if ( C4::Context->preference('SeparateHoldings') ) {
319     my $SeparateHoldingsBranch = C4::Context->preference('SeparateHoldingsBranch') || 'homebranch';
320     my $other_holdings_count = $items_to_display->search({ $SeparateHoldingsBranch => { '!=' => C4::Context->userenv->{branch} } })->count;
321     $template->param( other_holdings_count => $other_holdings_count );
322 }
323 $template->param(
324     count => $all_items->count, # FIXME 'count' is used in catalog-strings.inc
325                                 # But it's not a meaningful variable, we should rename it there
326     all_items_count => $all_items->count,
327     items_to_display_count => $items_to_display->count,
328 );
329
330 my $some_private_shelves = Koha::Virtualshelves->get_some_shelves(
331     {
332         borrowernumber => $borrowernumber,
333         add_allowed    => 1,
334         public         => 0,
335     }
336 );
337 my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
338     {
339         borrowernumber => $borrowernumber,
340         add_allowed    => 1,
341         public         => 1,
342     }
343 );
344
345
346 $template->param(
347     add_to_some_private_shelves => $some_private_shelves,
348     add_to_some_public_shelves  => $some_public_shelves,
349 );
350
351 $template->param(
352     MARCNOTES               => !$invalid_marc_record ? $biblio->get_marc_notes() : undef,
353     z3950_search_params     => C4::Search::z3950_search_args($dat),
354     C4::Search::enabled_staff_search_views,
355 );
356
357 if (C4::Context->preference("AlternateHoldingsField") && $items_to_display->count == 0) {
358     my $fieldspec = C4::Context->preference("AlternateHoldingsField");
359     my $subfields = substr $fieldspec, 3;
360     my $holdingsep = C4::Context->preference("AlternateHoldingsSeparator") || ' ';
361     my @alternateholdingsinfo = ();
362     my @holdingsfields = $marc_record->field(substr $fieldspec, 0, 3);
363
364     for my $field (@holdingsfields) {
365         my %holding = ( holding => '' );
366         my $havesubfield = 0;
367         for my $subfield ($field->subfields()) {
368             if ((index $subfields, $$subfield[0]) >= 0) {
369                 $holding{'holding'} .= $holdingsep if (length $holding{'holding'} > 0);
370                 $holding{'holding'} .= $$subfield[1];
371                 $havesubfield++;
372             }
373         }
374         if ($havesubfield) {
375             push(@alternateholdingsinfo, \%holding);
376         }
377     }
378
379     $template->param(
380         ALTERNATEHOLDINGS   => \@alternateholdingsinfo,
381         );
382 }
383
384 my @results = ( $dat, );
385 foreach ( keys %{$dat} ) {
386     $template->param( "$_" => defined $dat->{$_} ? $dat->{$_} : '' );
387 }
388
389 # does not work: my %views_enabled = map { $_ => 1 } $template->query(loop => 'EnableViews');
390 # method query not found?!?!
391 $template->param( AmazonTld => get_amazon_tld() ) if ( C4::Context->preference("AmazonCoverImages"));
392 $template->param(
393     biblionumber        => $biblionumber,
394     ($analyze? 'analyze':'detailview') =>1,
395     subscriptions       => \@subs,
396     subscriptionsnumber => $subscriptionsnumber,
397     subscriptiontitle   => $dat->{title},
398     searchid            => scalar $query->param('searchid'),
399 );
400
401 # Lists
402
403 if (C4::Context->preference("virtualshelves") ) {
404     my $shelves = Koha::Virtualshelves->search(
405         {
406             biblionumber => $biblionumber,
407             public => 1,
408         },
409         {
410             join => 'virtualshelfcontents',
411         }
412     );
413     $template->param( 'shelves' => $shelves );
414 }
415
416 # XISBN Stuff
417 if (C4::Context->preference("FRBRizeEditions")==1) {
418     eval {
419         $template->param(
420             XISBNS => scalar get_xisbns($isbn, $biblionumber)
421         );
422     };
423     if ($@) { warn "XISBN Failed $@"; }
424 }
425
426 if ( C4::Context->preference("LocalCoverImages") == 1 ) {
427     my $images = $biblio->cover_images;
428     $template->param(
429         localimages => $biblio->cover_images->search(
430             {}, { order_by => [ \"COALESCE(itemnumber, 0, 1)", 'timestamp' ] }
431         ),
432     );
433 }
434
435 # HTML5 Media
436 if ( (C4::Context->preference("HTML5MediaEnabled") eq 'both') or (C4::Context->preference("HTML5MediaEnabled") eq 'staff') ) {
437     $template->param( C4::HTML5Media->gethtml5media($marc_record));
438 }
439
440 # Displaying tags
441 my $tag_quantity;
442 if (C4::Context->preference('TagsEnabled') and $tag_quantity = C4::Context->preference('TagsShowOnDetail')) {
443     $template->param(
444         TagsEnabled => 1,
445         TagsShowOnDetail => $tag_quantity
446     );
447     $template->param(TagLoop => get_tags({biblionumber=>$biblionumber, approved=>1,
448                                 'sort'=>'-weight', limit=>$tag_quantity}));
449 }
450
451 #we only need to pass the number of holds to the template
452 my $holds = $biblio->holds;
453 $template->param( holdcount => $holds->count );
454
455 # Check if there are any ILL requests connected to the biblio
456 my $illrequests =
457     C4::Context->preference('ILLModule')
458   ? Koha::Illrequests->search( { biblio_id => $biblionumber } )
459   : [];
460 $template->param( illrequests => $illrequests );
461
462 my $StaffDetailItemSelection = C4::Context->preference('StaffDetailItemSelection');
463 if ($StaffDetailItemSelection) {
464     # Only enable item selection if user can execute at least one action
465     if (
466         $flags->{superlibrarian}
467         || (
468             ref $flags->{tools} eq 'HASH' && (
469                 $flags->{tools}->{items_batchmod}       # Modify selected items
470                 || $flags->{tools}->{items_batchdel}    # Delete selected items
471             )
472         )
473         || ( ref $flags->{tools} eq '' && $flags->{tools} )
474       )
475     {
476         $template->param(
477             StaffDetailItemSelection => $StaffDetailItemSelection );
478     }
479 }
480
481 # get biblionumbers stored in the cart
482 my @cart_list;
483
484 if($query->cookie("intranet_bib_list")){
485     my $cart_list = $query->cookie("intranet_bib_list");
486     @cart_list = split(/\//, $cart_list);
487     if ( grep {$_ eq $biblionumber} @cart_list) {
488         $template->param( incart => 1 );
489     }
490 }
491
492 if ( C4::Context->preference('UseCourseReserves') ) {
493     my $course_reserves = GetItemCourseReservesInfo( biblionumber => $biblionumber );
494     $template->param( course_reserves => $course_reserves );
495 }
496
497 my @libraries = $biblio->items($items_params)->get_column('homebranch');
498 my %can_edit_items_from = map {
499     $_ => $patron->can_edit_items_from($_)
500 } @libraries;
501 $template->param(can_edit_items_from => \%can_edit_items_from);
502
503 my @itemtypes = Koha::ItemTypes->search->as_list;
504 my %item_type_image_locations = map {
505     $_->itemtype => $_->image_location('intranet')
506 } @itemtypes;
507 $template->param(item_type_image_locations => \%item_type_image_locations);
508
509
510 $template->param(found1 => scalar $query->param('found1') );
511
512 $template->param(biblio => $biblio);
513
514 output_html_with_http_headers $query, $cookie, $template->output;