Update release notes for 21.05.18 release
[koha.git] / Koha / Biblio.pm
1 package Koha::Biblio;
2
3 # Copyright ByWater Solutions 2014
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use Carp;
23 use List::MoreUtils qw(any);
24 use URI;
25 use URI::Escape;
26
27 use C4::Koha;
28 use C4::Biblio qw();
29
30 use Koha::Database;
31 use Koha::DateUtils qw( dt_from_string );
32
33 use base qw(Koha::Object);
34
35 use Koha::Acquisition::Orders;
36 use Koha::ArticleRequest::Status;
37 use Koha::ArticleRequests;
38 use Koha::Biblio::Metadatas;
39 use Koha::Biblioitems;
40 use Koha::CirculationRules;
41 use Koha::Item::Transfer::Limits;
42 use Koha::Items;
43 use Koha::Libraries;
44 use Koha::Suggestions;
45 use Koha::Subscriptions;
46
47 =head1 NAME
48
49 Koha::Biblio - Koha Biblio Object class
50
51 =head1 API
52
53 =head2 Class Methods
54
55 =cut
56
57 =head3 store
58
59 Overloaded I<store> method to set default values
60
61 =cut
62
63 sub store {
64     my ( $self ) = @_;
65
66     $self->datecreated( dt_from_string ) unless $self->datecreated;
67
68     return $self->SUPER::store;
69 }
70
71 =head3 metadata
72
73 my $metadata = $biblio->metadata();
74
75 Returns a Koha::Biblio::Metadata object
76
77 =cut
78
79 sub metadata {
80     my ( $self ) = @_;
81
82     my $metadata = $self->_result->metadata;
83     return Koha::Biblio::Metadata->_new_from_dbic($metadata);
84 }
85
86 =head3 orders
87
88 my $orders = $biblio->orders();
89
90 Returns a Koha::Acquisition::Orders object
91
92 =cut
93
94 sub orders {
95     my ( $self ) = @_;
96
97     my $orders = $self->_result->orders;
98     return Koha::Acquisition::Orders->_new_from_dbic($orders);
99 }
100
101 =head3 active_orders
102
103 my $active_orders = $biblio->active_orders();
104
105 Returns the active acquisition orders related to this biblio.
106 An order is considered active when it is not cancelled (i.e. when datecancellation
107 is not undef).
108
109 =cut
110
111 sub active_orders {
112     my ( $self ) = @_;
113
114     return $self->orders->search({ datecancellationprinted => undef });
115 }
116
117 =head3 can_article_request
118
119 my $bool = $biblio->can_article_request( $borrower );
120
121 Returns true if article requests can be made for this record
122
123 $borrower must be a Koha::Patron object
124
125 =cut
126
127 sub can_article_request {
128     my ( $self, $borrower ) = @_;
129
130     my $rule = $self->article_request_type($borrower);
131     return q{} if $rule eq 'item_only' && !$self->items()->count();
132     return 1 if $rule && $rule ne 'no';
133
134     return q{};
135 }
136
137 =head3 can_be_transferred
138
139 $biblio->can_be_transferred({ to => $to_library, from => $from_library })
140
141 Checks if at least one item of a biblio can be transferred to given library.
142
143 This feature is controlled by two system preferences:
144 UseBranchTransferLimits to enable / disable the feature
145 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
146                          for setting the limitations
147
148 Performance-wise, it is recommended to use this method for a biblio instead of
149 iterating each item of a biblio with Koha::Item->can_be_transferred().
150
151 Takes HASHref that can have the following parameters:
152     MANDATORY PARAMETERS:
153     $to   : Koha::Library
154     OPTIONAL PARAMETERS:
155     $from : Koha::Library # if given, only items from that
156                           # holdingbranch are considered
157
158 Returns 1 if at least one of the item of a biblio can be transferred
159 to $to_library, otherwise 0.
160
161 =cut
162
163 sub can_be_transferred {
164     my ($self, $params) = @_;
165
166     my $to   = $params->{to};
167     my $from = $params->{from};
168
169     return 1 unless C4::Context->preference('UseBranchTransferLimits');
170     my $limittype = C4::Context->preference('BranchTransferLimitsType');
171
172     my $items;
173     foreach my $item_of_bib ($self->items->as_list) {
174         next unless $item_of_bib->holdingbranch;
175         next if $from && $from->branchcode ne $item_of_bib->holdingbranch;
176         return 1 if $item_of_bib->holdingbranch eq $to->branchcode;
177         my $code = $limittype eq 'itemtype'
178             ? $item_of_bib->effective_itemtype
179             : $item_of_bib->ccode;
180         return 1 unless $code;
181         $items->{$code}->{$item_of_bib->holdingbranch} = 1;
182     }
183
184     # At this point we will have a HASHref containing each itemtype/ccode that
185     # this biblio has, inside which are all of the holdingbranches where those
186     # items are located at. Then, we will query Koha::Item::Transfer::Limits to
187     # find out whether a transfer limits for such $limittype from any of the
188     # listed holdingbranches to the given $to library exist. If at least one
189     # holdingbranch for that $limittype does not have a transfer limit to given
190     # $to library, then we know that the transfer is possible.
191     foreach my $code (keys %{$items}) {
192         my @holdingbranches = keys %{$items->{$code}};
193         return 1 if Koha::Item::Transfer::Limits->search({
194             toBranch => $to->branchcode,
195             fromBranch => { 'in' => \@holdingbranches },
196             $limittype => $code
197         }, {
198             group_by => [qw/fromBranch/]
199         })->count == scalar(@holdingbranches) ? 0 : 1;
200     }
201
202     return 0;
203 }
204
205
206 =head3 pickup_locations
207
208     my $pickup_locations = $biblio->pickup_locations( {patron => $patron } );
209
210 Returns a Koha::Libraries set of possible pickup locations for this biblio's items,
211 according to patron's home library (if patron is defined and holds are allowed
212 only from hold groups) and if item can be transferred to each pickup location.
213
214 =cut
215
216 sub pickup_locations {
217     my ( $self, $params ) = @_;
218
219     my $patron = $params->{patron};
220
221     my @pickup_locations;
222     foreach my $item_of_bib ( $self->items->as_list ) {
223         push @pickup_locations,
224           $item_of_bib->pickup_locations( { patron => $patron } )
225           ->_resultset->get_column('branchcode')->all;
226     }
227
228     return Koha::Libraries->search(
229         { branchcode => { '-in' => \@pickup_locations } }, { order_by => ['branchname'] } );
230 }
231
232 =head3 hidden_in_opac
233
234     my $bool = $biblio->hidden_in_opac({ [ rules => $rules ] })
235
236 Returns true if the biblio matches the hidding criteria defined in $rules.
237 Returns false otherwise. It involves the I<OpacHiddenItems> and
238 I<OpacHiddenItemsHidesRecord> system preferences.
239
240 Takes HASHref that can have the following parameters:
241     OPTIONAL PARAMETERS:
242     $rules : { <field> => [ value_1, ... ], ... }
243
244 Note: $rules inherits its structure from the parsed YAML from reading
245 the I<OpacHiddenItems> system preference.
246
247 =cut
248
249 sub hidden_in_opac {
250     my ( $self, $params ) = @_;
251
252     my $rules = $params->{rules} // {};
253
254     my @items = $self->items->as_list;
255
256     return 0 unless @items; # Do not hide if there is no item
257
258     # Ok, there are items, don't even try the rules unless OpacHiddenItemsHidesRecord
259     return 0 unless C4::Context->preference('OpacHiddenItemsHidesRecord');
260
261     return !(any { !$_->hidden_in_opac({ rules => $rules }) } @items);
262 }
263
264 =head3 article_request_type
265
266 my $type = $biblio->article_request_type( $borrower );
267
268 Returns the article request type based on items, or on the record
269 itself if there are no items.
270
271 $borrower must be a Koha::Patron object
272
273 =cut
274
275 sub article_request_type {
276     my ( $self, $borrower ) = @_;
277
278     return q{} unless $borrower;
279
280     my $rule = $self->article_request_type_for_items( $borrower );
281     return $rule if $rule;
282
283     # If the record has no items that are requestable, go by the record itemtype
284     $rule = $self->article_request_type_for_bib($borrower);
285     return $rule if $rule;
286
287     return q{};
288 }
289
290 =head3 article_request_type_for_bib
291
292 my $type = $biblio->article_request_type_for_bib
293
294 Returns the article request type 'yes', 'no', 'item_only', 'bib_only', for the given record
295
296 =cut
297
298 sub article_request_type_for_bib {
299     my ( $self, $borrower ) = @_;
300
301     return q{} unless $borrower;
302
303     my $borrowertype = $borrower->categorycode;
304     my $itemtype     = $self->itemtype();
305
306     my $rule = Koha::CirculationRules->get_effective_rule(
307         {
308             rule_name    => 'article_requests',
309             categorycode => $borrowertype,
310             itemtype     => $itemtype,
311         }
312     );
313
314     return q{} unless $rule;
315     return $rule->rule_value || q{}
316 }
317
318 =head3 article_request_type_for_items
319
320 my $type = $biblio->article_request_type_for_items
321
322 Returns the article request type 'yes', 'no', 'item_only', 'bib_only', for the given record's items
323
324 If there is a conflict where some items are 'bib_only' and some are 'item_only', 'bib_only' will be returned.
325
326 =cut
327
328 sub article_request_type_for_items {
329     my ( $self, $borrower ) = @_;
330
331     my $counts;
332     foreach my $item ( $self->items()->as_list() ) {
333         my $rule = $item->article_request_type($borrower);
334         return $rule if $rule eq 'bib_only';    # we don't need to go any further
335         $counts->{$rule}++;
336     }
337
338     return 'item_only' if $counts->{item_only};
339     return 'yes'       if $counts->{yes};
340     return 'no'        if $counts->{no};
341     return q{};
342 }
343
344 =head3 article_requests
345
346 my @requests = $biblio->article_requests
347
348 Returns the article requests associated with this Biblio
349
350 =cut
351
352 sub article_requests {
353     my ( $self, $borrower ) = @_;
354
355     $self->{_article_requests} ||= Koha::ArticleRequests->search( { biblionumber => $self->biblionumber() } );
356
357     return wantarray ? $self->{_article_requests}->as_list : $self->{_article_requests};
358 }
359
360 =head3 article_requests_current
361
362 my @requests = $biblio->article_requests_current
363
364 Returns the article requests associated with this Biblio that are incomplete
365
366 =cut
367
368 sub article_requests_current {
369     my ( $self, $borrower ) = @_;
370
371     $self->{_article_requests_current} ||= Koha::ArticleRequests->search(
372         {
373             biblionumber => $self->biblionumber(),
374             -or          => [
375                 { status => Koha::ArticleRequest::Status::Pending },
376                 { status => Koha::ArticleRequest::Status::Processing }
377             ]
378         }
379     );
380
381     return wantarray ? $self->{_article_requests_current}->as_list : $self->{_article_requests_current};
382 }
383
384 =head3 article_requests_finished
385
386 my @requests = $biblio->article_requests_finished
387
388 Returns the article requests associated with this Biblio that are completed
389
390 =cut
391
392 sub article_requests_finished {
393     my ( $self, $borrower ) = @_;
394
395     $self->{_article_requests_finished} ||= Koha::ArticleRequests->search(
396         {
397             biblionumber => $self->biblionumber(),
398             -or          => [
399                 { status => Koha::ArticleRequest::Status::Completed },
400                 { status => Koha::ArticleRequest::Status::Canceled }
401             ]
402         }
403     );
404
405     return wantarray ? $self->{_article_requests_finished}->as_list : $self->{_article_requests_finished};
406 }
407
408 =head3 items
409
410 my $items = $biblio->items();
411
412 Returns the related Koha::Items object for this biblio
413
414 =cut
415
416 sub items {
417     my ($self) = @_;
418
419     my $items_rs = $self->_result->items;
420
421     return Koha::Items->_new_from_dbic( $items_rs );
422 }
423
424 =head3 itemtype
425
426 my $itemtype = $biblio->itemtype();
427
428 Returns the itemtype for this record.
429
430 =cut
431
432 sub itemtype {
433     my ( $self ) = @_;
434
435     return $self->biblioitem()->itemtype();
436 }
437
438 =head3 holds
439
440 my $holds = $biblio->holds();
441
442 return the current holds placed on this record
443
444 =cut
445
446 sub holds {
447     my ( $self, $params, $attributes ) = @_;
448     $attributes->{order_by} = 'priority' unless exists $attributes->{order_by};
449     my $hold_rs = $self->_result->reserves->search( $params, $attributes );
450     return Koha::Holds->_new_from_dbic($hold_rs);
451 }
452
453 =head3 current_holds
454
455 my $holds = $biblio->current_holds
456
457 Return the holds placed on this bibliographic record.
458 It does not include future holds.
459
460 =cut
461
462 sub current_holds {
463     my ($self) = @_;
464     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
465     return $self->holds(
466         { reservedate => { '<=' => $dtf->format_date(dt_from_string) } } );
467 }
468
469 =head3 biblioitem
470
471 my $field = $self->biblioitem()->itemtype
472
473 Returns the related Koha::Biblioitem object for this Biblio object
474
475 =cut
476
477 sub biblioitem {
478     my ($self) = @_;
479
480     $self->{_biblioitem} ||= Koha::Biblioitems->find( { biblionumber => $self->biblionumber() } );
481
482     return $self->{_biblioitem};
483 }
484
485 =head3 suggestions
486
487 my $suggestions = $self->suggestions
488
489 Returns the related Koha::Suggestions object for this Biblio object
490
491 =cut
492
493 sub suggestions {
494     my ($self) = @_;
495
496     my $suggestions_rs = $self->_result->suggestions;
497     return Koha::Suggestions->_new_from_dbic( $suggestions_rs );
498 }
499
500 =head3 subscriptions
501
502 my $subscriptions = $self->subscriptions
503
504 Returns the related Koha::Subscriptions object for this Biblio object
505
506 =cut
507
508 sub subscriptions {
509     my ($self) = @_;
510
511     $self->{_subscriptions} ||= Koha::Subscriptions->search( { biblionumber => $self->biblionumber } );
512
513     return $self->{_subscriptions};
514 }
515
516 =head3 has_items_waiting_or_intransit
517
518 my $itemsWaitingOrInTransit = $biblio->has_items_waiting_or_intransit
519
520 Tells if this bibliographic record has items waiting or in transit.
521
522 =cut
523
524 sub has_items_waiting_or_intransit {
525     my ( $self ) = @_;
526
527     if ( Koha::Holds->search({ biblionumber => $self->id,
528                                found => ['W', 'T'] })->count ) {
529         return 1;
530     }
531
532     foreach my $item ( $self->items->as_list ) {
533         return 1 if $item->get_transfer;
534     }
535
536     return 0;
537 }
538
539 =head2 get_coins
540
541 my $coins = $biblio->get_coins;
542
543 Returns the COinS (a span) which can be included in a biblio record
544
545 =cut
546
547 sub get_coins {
548     my ( $self ) = @_;
549
550     my $record = $self->metadata->record;
551
552     my $pos7 = substr $record->leader(), 7, 1;
553     my $pos6 = substr $record->leader(), 6, 1;
554     my $mtx;
555     my $genre;
556     my ( $aulast, $aufirst ) = ( '', '' );
557     my @authors;
558     my $title;
559     my $hosttitle;
560     my $pubyear   = '';
561     my $isbn      = '';
562     my $issn      = '';
563     my $publisher = '';
564     my $pages     = '';
565     my $titletype = '';
566
567     # For the purposes of generating COinS metadata, LDR/06-07 can be
568     # considered the same for UNIMARC and MARC21
569     my $fmts6 = {
570         'a' => 'book',
571         'b' => 'manuscript',
572         'c' => 'book',
573         'd' => 'manuscript',
574         'e' => 'map',
575         'f' => 'map',
576         'g' => 'film',
577         'i' => 'audioRecording',
578         'j' => 'audioRecording',
579         'k' => 'artwork',
580         'l' => 'document',
581         'm' => 'computerProgram',
582         'o' => 'document',
583         'r' => 'document',
584     };
585     my $fmts7 = {
586         'a' => 'journalArticle',
587         's' => 'journal',
588     };
589
590     $genre = $fmts6->{$pos6} ? $fmts6->{$pos6} : 'book';
591
592     if ( $genre eq 'book' ) {
593             $genre = $fmts7->{$pos7} if $fmts7->{$pos7};
594     }
595
596     ##### We must transform mtx to a valable mtx and document type ####
597     if ( $genre eq 'book' ) {
598             $mtx = 'book';
599             $titletype = 'b';
600     } elsif ( $genre eq 'journal' ) {
601             $mtx = 'journal';
602             $titletype = 'j';
603     } elsif ( $genre eq 'journalArticle' ) {
604             $mtx   = 'journal';
605             $genre = 'article';
606             $titletype = 'a';
607     } else {
608             $mtx = 'dc';
609     }
610
611     if ( C4::Context->preference("marcflavour") eq "UNIMARC" ) {
612
613         # Setting datas
614         $aulast  = $record->subfield( '700', 'a' ) || '';
615         $aufirst = $record->subfield( '700', 'b' ) || '';
616         push @authors, "$aufirst $aulast" if ($aufirst or $aulast);
617
618         # others authors
619         if ( $record->field('200') ) {
620             for my $au ( $record->field('200')->subfield('g') ) {
621                 push @authors, $au;
622             }
623         }
624
625         $title     = $record->subfield( '200', 'a' );
626         my $subfield_210d = $record->subfield('210', 'd');
627         if ($subfield_210d and $subfield_210d =~ /(\d{4})/) {
628             $pubyear = $1;
629         }
630         $publisher = $record->subfield( '210', 'c' ) || '';
631         $isbn      = $record->subfield( '010', 'a' ) || '';
632         $issn      = $record->subfield( '011', 'a' ) || '';
633     } else {
634
635         # MARC21 need some improve
636
637         # Setting datas
638         if ( $record->field('100') ) {
639             push @authors, $record->subfield( '100', 'a' );
640         }
641
642         # others authors
643         if ( $record->field('700') ) {
644             for my $au ( $record->field('700')->subfield('a') ) {
645                 push @authors, $au;
646             }
647         }
648         $title = $record->field('245');
649         $title &&= $title->as_string('ab');
650         if ($titletype eq 'a') {
651             $pubyear   = $record->field('008') || '';
652             $pubyear   = substr($pubyear->data(), 7, 4) if $pubyear;
653             $isbn      = $record->subfield( '773', 'z' ) || '';
654             $issn      = $record->subfield( '773', 'x' ) || '';
655             $hosttitle = $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{};
656             my @rels = $record->subfield( '773', 'g' );
657             $pages = join(', ', @rels);
658         } else {
659             $pubyear   = $record->subfield( '260', 'c' ) || '';
660             $publisher = $record->subfield( '260', 'b' ) || '';
661             $isbn      = $record->subfield( '020', 'a' ) || '';
662             $issn      = $record->subfield( '022', 'a' ) || '';
663         }
664
665     }
666
667     my @params = (
668         [ 'ctx_ver', 'Z39.88-2004' ],
669         [ 'rft_val_fmt', "info:ofi/fmt:kev:mtx:$mtx" ],
670         [ ($mtx eq 'dc' ? 'rft.type' : 'rft.genre'), $genre ],
671         [ "rft.${titletype}title", $title ],
672     );
673
674     # rft.title is authorized only once, so by checking $titletype
675     # we ensure that rft.title is not already in the list.
676     if ($hosttitle and $titletype) {
677         push @params, [ 'rft.title', $hosttitle ];
678     }
679
680     push @params, (
681         [ 'rft.isbn', $isbn ],
682         [ 'rft.issn', $issn ],
683     );
684
685     # If it's a subscription, these informations have no meaning.
686     if ($genre ne 'journal') {
687         push @params, (
688             [ 'rft.aulast', $aulast ],
689             [ 'rft.aufirst', $aufirst ],
690             (map { [ 'rft.au', $_ ] } @authors),
691             [ 'rft.pub', $publisher ],
692             [ 'rft.date', $pubyear ],
693             [ 'rft.pages', $pages ],
694         );
695     }
696
697     my $coins_value = join( '&amp;',
698         map { $$_[1] ? $$_[0] . '=' . uri_escape_utf8( $$_[1] ) : () } @params );
699
700     return $coins_value;
701 }
702
703 =head2 get_openurl
704
705 my $url = $biblio->get_openurl;
706
707 Returns url for OpenURL resolver set in OpenURLResolverURL system preference
708
709 =cut
710
711 sub get_openurl {
712     my ( $self ) = @_;
713
714     my $OpenURLResolverURL = C4::Context->preference('OpenURLResolverURL');
715
716     if ($OpenURLResolverURL) {
717         my $uri = URI->new($OpenURLResolverURL);
718
719         if (not defined $uri->query) {
720             $OpenURLResolverURL .= '?';
721         } else {
722             $OpenURLResolverURL .= '&amp;';
723         }
724         $OpenURLResolverURL .= $self->get_coins;
725     }
726
727     return $OpenURLResolverURL;
728 }
729
730 =head3 is_serial
731
732 my $serial = $biblio->is_serial
733
734 Return boolean true if this bibbliographic record is continuing resource
735
736 =cut
737
738 sub is_serial {
739     my ( $self ) = @_;
740
741     return 1 if $self->serial;
742
743     my $record = $self->metadata->record;
744     return 1 if substr($record->leader, 7, 1) eq 's';
745
746     return 0;
747 }
748
749 =head3 custom_cover_image_url
750
751 my $image_url = $biblio->custom_cover_image_url
752
753 Return the specific url of the cover image for this bibliographic record.
754 It is built regaring the value of the system preference CustomCoverImagesURL
755
756 =cut
757
758 sub custom_cover_image_url {
759     my ( $self ) = @_;
760     my $url = C4::Context->preference('CustomCoverImagesURL');
761     if ( $url =~ m|{isbn}| ) {
762         my $isbn = $self->biblioitem->isbn;
763         return unless $isbn;
764         $url =~ s|{isbn}|$isbn|g;
765     }
766     if ( $url =~ m|{normalized_isbn}| ) {
767         my $normalized_isbn = C4::Koha::GetNormalizedISBN($self->biblioitem->isbn);
768         return unless $normalized_isbn;
769         $url =~ s|{normalized_isbn}|$normalized_isbn|g;
770     }
771     if ( $url =~ m|{issn}| ) {
772         my $issn = $self->biblioitem->issn;
773         return unless $issn;
774         $url =~ s|{issn}|$issn|g;
775     }
776
777     my $re = qr|{(?<field>\d{3})(\$(?<subfield>.))?}|;
778     if ( $url =~ $re ) {
779         my $field = $+{field};
780         my $subfield = $+{subfield};
781         my $marc_record = $self->metadata->record;
782         my $value;
783         if ( $subfield ) {
784             $value = $marc_record->subfield( $field, $subfield );
785         } else {
786             my $controlfield = $marc_record->field($field);
787             $value = $controlfield->data() if $controlfield;
788         }
789         return unless $value;
790         $url =~ s|$re|$value|;
791     }
792
793     return $url;
794 }
795
796 =head3 cover_images
797
798 Return the cover images associated with this biblio.
799
800 =cut
801
802 sub cover_images {
803     my ( $self ) = @_;
804
805     my $cover_images_rs = $self->_result->cover_images;
806     return unless $cover_images_rs;
807     return Koha::CoverImages->_new_from_dbic($cover_images_rs);
808 }
809
810 =head3 get_marc_notes
811
812     $marcnotesarray = $biblio->get_marc_notes({ marcflavour => $marcflavour });
813
814 Get all notes from the MARC record and returns them in an array.
815 The notes are stored in different fields depending on MARC flavour.
816 MARC21 5XX $u subfields receive special attention as they are URIs.
817
818 =cut
819
820 sub get_marc_notes {
821     my ( $self, $params ) = @_;
822
823     my $marcflavour = $params->{marcflavour};
824     my $opac = $params->{opac};
825
826     my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
827     my @marcnotes;
828
829     #MARC21 specs indicate some notes should be private if first indicator 0
830     my %maybe_private = (
831         541 => 1,
832         542 => 1,
833         561 => 1,
834         583 => 1,
835         590 => 1
836     );
837
838     my %hiddenlist = map { $_ => 1 }
839         split( /,/, C4::Context->preference('NotesToHide'));
840     foreach my $field ( $self->metadata->record->field($scope) ) {
841         my $tag = $field->tag();
842         next if $hiddenlist{ $tag };
843         next if $opac && $maybe_private{$tag} && !$field->indicator(1);
844         if( $marcflavour ne 'UNIMARC' && $field->subfield('u') ) {
845             # Field 5XX$u always contains URI
846             # Examples: 505u, 506u, 510u, 514u, 520u, 530u, 538u, 540u, 542u, 552u, 555u, 561u, 563u, 583u
847             # We first push the other subfields, then all $u's separately
848             # Leave further actions to the template (see e.g. opac-detail)
849             my $othersub =
850                 join '', ( 'a' .. 't', 'v' .. 'z', '0' .. '9' ); # excl 'u'
851             push @marcnotes, { marcnote => $field->as_string($othersub) };
852             foreach my $sub ( $field->subfield('u') ) {
853                 $sub =~ s/^\s+|\s+$//g; # trim
854                 push @marcnotes, { marcnote => $sub };
855             }
856         } else {
857             push @marcnotes, { marcnote => $field->as_string() };
858         }
859     }
860     return \@marcnotes;
861 }
862
863 =head3 to_api
864
865     my $json = $biblio->to_api;
866
867 Overloaded method that returns a JSON representation of the Koha::Biblio object,
868 suitable for API output. The related Koha::Biblioitem object is merged as expected
869 on the API.
870
871 =cut
872
873 sub to_api {
874     my ($self, $args) = @_;
875
876     my $response = $self->SUPER::to_api( $args );
877     my $biblioitem = $self->biblioitem->to_api;
878
879     return { %$response, %$biblioitem };
880 }
881
882 =head3 to_api_mapping
883
884 This method returns the mapping for representing a Koha::Biblio object
885 on the API.
886
887 =cut
888
889 sub to_api_mapping {
890     return {
891         biblionumber     => 'biblio_id',
892         frameworkcode    => 'framework_id',
893         unititle         => 'uniform_title',
894         seriestitle      => 'series_title',
895         copyrightdate    => 'copyright_date',
896         datecreated      => 'creation_date'
897     };
898 }
899
900 =head2 Internal methods
901
902 =head3 type
903
904 =cut
905
906 sub _type {
907     return 'Biblio';
908 }
909
910 =head1 AUTHOR
911
912 Kyle M Hall <kyle@bywatersolutions.com>
913
914 =cut
915
916 1;