3 # Copyright ByWater Solutions 2014
5 # This file is part of Koha.
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.
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.
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>.
23 use List
::MoreUtils
qw(any);
31 use Koha
::DateUtils
qw( dt_from_string );
33 use base
qw(Koha::Object);
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
;
44 use Koha
::Suggestions
;
45 use Koha
::Subscriptions
;
49 Koha::Biblio - Koha Biblio Object class
59 Overloaded I<store> method to set default values
66 $self->datecreated( dt_from_string
) unless $self->datecreated;
68 return $self->SUPER::store
;
73 my $metadata = $biblio->metadata();
75 Returns a Koha::Biblio::Metadata object
82 my $metadata = $self->_result->metadata;
83 return Koha
::Biblio
::Metadata
->_new_from_dbic($metadata);
88 my $orders = $biblio->orders();
90 Returns a Koha::Acquisition::Orders object
97 my $orders = $self->_result->orders;
98 return Koha
::Acquisition
::Orders
->_new_from_dbic($orders);
103 my $active_orders = $biblio->active_orders();
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
114 return $self->orders->search({ datecancellationprinted
=> undef });
117 =head3 can_article_request
119 my $bool = $biblio->can_article_request( $borrower );
121 Returns true if article requests can be made for this record
123 $borrower must be a Koha::Patron object
127 sub can_article_request
{
128 my ( $self, $borrower ) = @_;
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';
137 =head3 can_be_transferred
139 $biblio->can_be_transferred({ to => $to_library, from => $from_library })
141 Checks if at least one item of a biblio can be transferred to given library.
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
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().
151 Takes HASHref that can have the following parameters:
152 MANDATORY PARAMETERS:
155 $from : Koha::Library # if given, only items from that
156 # holdingbranch are considered
158 Returns 1 if at least one of the item of a biblio can be transferred
159 to $to_library, otherwise 0.
163 sub can_be_transferred
{
164 my ($self, $params) = @_;
166 my $to = $params->{to
};
167 my $from = $params->{from
};
169 return 1 unless C4
::Context
->preference('UseBranchTransferLimits');
170 my $limittype = C4
::Context
->preference('BranchTransferLimitsType');
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;
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 },
198 group_by
=> [qw
/fromBranch/]
199 })->count == scalar(@holdingbranches) ?
0 : 1;
206 =head3 pickup_locations
208 @pickup_locations = $biblio->pickup_locations( {patron => $patron } )
210 Returns possible pickup locations for this biblio items, according to patron's home library (if patron is defined and holds are allowed only from hold groups)
211 and if item can be transferred to each pickup location.
215 sub pickup_locations
{
216 my ($self, $params) = @_;
218 my $patron = $params->{patron
};
220 my @pickup_locations;
221 foreach my $item_of_bib ($self->items->as_list) {
222 push @pickup_locations, $item_of_bib->pickup_locations( {patron
=> $patron} );
227 grep { !$seen{ $_->branchcode }++ } @pickup_locations;
229 return wantarray ?
@pickup_locations : \
@pickup_locations;
232 =head3 hidden_in_opac
234 my $bool = $biblio->hidden_in_opac({ [ rules => $rules ] })
236 Returns true if the biblio matches the hidding criteria defined in $rules.
237 Returns false otherwise.
239 Takes HASHref that can have the following parameters:
241 $rules : { <field> => [ value_1, ... ], ... }
243 Note: $rules inherits its structure from the parsed YAML from reading
244 the I<OpacHiddenItems> system preference.
249 my ( $self, $params ) = @_;
251 my $rules = $params->{rules
} // {};
253 my @items = $self->items->as_list;
255 return 0 unless @items; # Do not hide if there is no item
257 return !(any
{ !$_->hidden_in_opac({ rules
=> $rules }) } @items);
260 =head3 article_request_type
262 my $type = $biblio->article_request_type( $borrower );
264 Returns the article request type based on items, or on the record
265 itself if there are no items.
267 $borrower must be a Koha::Patron object
271 sub article_request_type
{
272 my ( $self, $borrower ) = @_;
274 return q{} unless $borrower;
276 my $rule = $self->article_request_type_for_items( $borrower );
277 return $rule if $rule;
279 # If the record has no items that are requestable, go by the record itemtype
280 $rule = $self->article_request_type_for_bib($borrower);
281 return $rule if $rule;
286 =head3 article_request_type_for_bib
288 my $type = $biblio->article_request_type_for_bib
290 Returns the article request type 'yes', 'no', 'item_only', 'bib_only', for the given record
294 sub article_request_type_for_bib
{
295 my ( $self, $borrower ) = @_;
297 return q{} unless $borrower;
299 my $borrowertype = $borrower->categorycode;
300 my $itemtype = $self->itemtype();
302 my $rule = Koha
::CirculationRules
->get_effective_rule(
304 rule_name
=> 'article_requests',
305 categorycode
=> $borrowertype,
306 itemtype
=> $itemtype,
310 return q{} unless $rule;
311 return $rule->rule_value || q{}
314 =head3 article_request_type_for_items
316 my $type = $biblio->article_request_type_for_items
318 Returns the article request type 'yes', 'no', 'item_only', 'bib_only', for the given record's items
320 If there is a conflict where some items are 'bib_only' and some are 'item_only', 'bib_only' will be returned.
324 sub article_request_type_for_items
{
325 my ( $self, $borrower ) = @_;
328 foreach my $item ( $self->items()->as_list() ) {
329 my $rule = $item->article_request_type($borrower);
330 return $rule if $rule eq 'bib_only'; # we don't need to go any further
334 return 'item_only' if $counts->{item_only
};
335 return 'yes' if $counts->{yes
};
336 return 'no' if $counts->{no};
340 =head3 article_requests
342 my @requests = $biblio->article_requests
344 Returns the article requests associated with this Biblio
348 sub article_requests
{
349 my ( $self, $borrower ) = @_;
351 $self->{_article_requests
} ||= Koha
::ArticleRequests
->search( { biblionumber
=> $self->biblionumber() } );
353 return wantarray ?
$self->{_article_requests
}->as_list : $self->{_article_requests
};
356 =head3 article_requests_current
358 my @requests = $biblio->article_requests_current
360 Returns the article requests associated with this Biblio that are incomplete
364 sub article_requests_current
{
365 my ( $self, $borrower ) = @_;
367 $self->{_article_requests_current
} ||= Koha
::ArticleRequests
->search(
369 biblionumber
=> $self->biblionumber(),
371 { status
=> Koha
::ArticleRequest
::Status
::Pending
},
372 { status
=> Koha
::ArticleRequest
::Status
::Processing
}
377 return wantarray ?
$self->{_article_requests_current
}->as_list : $self->{_article_requests_current
};
380 =head3 article_requests_finished
382 my @requests = $biblio->article_requests_finished
384 Returns the article requests associated with this Biblio that are completed
388 sub article_requests_finished
{
389 my ( $self, $borrower ) = @_;
391 $self->{_article_requests_finished
} ||= Koha
::ArticleRequests
->search(
393 biblionumber
=> $self->biblionumber(),
395 { status
=> Koha
::ArticleRequest
::Status
::Completed
},
396 { status
=> Koha
::ArticleRequest
::Status
::Canceled
}
401 return wantarray ?
$self->{_article_requests_finished
}->as_list : $self->{_article_requests_finished
};
406 my $items = $biblio->items();
408 Returns the related Koha::Items object for this biblio
415 my $items_rs = $self->_result->items;
417 return Koha
::Items
->_new_from_dbic( $items_rs );
422 my $itemtype = $biblio->itemtype();
424 Returns the itemtype for this record.
431 return $self->biblioitem()->itemtype();
436 my $holds = $biblio->holds();
438 return the current holds placed on this record
443 my ( $self, $params, $attributes ) = @_;
444 $attributes->{order_by
} = 'priority' unless exists $attributes->{order_by
};
445 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
446 return Koha
::Holds
->_new_from_dbic($hold_rs);
451 my $holds = $biblio->current_holds
453 Return the holds placed on this bibliographic record.
454 It does not include future holds.
460 my $dtf = Koha
::Database
->new->schema->storage->datetime_parser;
462 { reservedate
=> { '<=' => $dtf->format_date(dt_from_string
) } } );
467 my $field = $self->biblioitem()->itemtype
469 Returns the related Koha::Biblioitem object for this Biblio object
476 $self->{_biblioitem
} ||= Koha
::Biblioitems
->find( { biblionumber
=> $self->biblionumber() } );
478 return $self->{_biblioitem
};
483 my $suggestions = $self->suggestions
485 Returns the related Koha::Suggestions object for this Biblio object
492 my $suggestions_rs = $self->_result->suggestions;
493 return Koha
::Suggestions
->_new_from_dbic( $suggestions_rs );
498 my $subscriptions = $self->subscriptions
500 Returns the related Koha::Subscriptions object for this Biblio object
507 $self->{_subscriptions
} ||= Koha
::Subscriptions
->search( { biblionumber
=> $self->biblionumber } );
509 return $self->{_subscriptions
};
512 =head3 has_items_waiting_or_intransit
514 my $itemsWaitingOrInTransit = $biblio->has_items_waiting_or_intransit
516 Tells if this bibliographic record has items waiting or in transit.
520 sub has_items_waiting_or_intransit
{
523 if ( Koha
::Holds
->search({ biblionumber
=> $self->id,
524 found
=> ['W', 'T'] })->count ) {
528 foreach my $item ( $self->items->as_list ) {
529 return 1 if $item->get_transfer;
537 my $coins = $biblio->get_coins;
539 Returns the COinS (a span) which can be included in a biblio record
546 my $record = $self->metadata->record;
548 my $pos7 = substr $record->leader(), 7, 1;
549 my $pos6 = substr $record->leader(), 6, 1;
552 my ( $aulast, $aufirst ) = ( '', '' );
563 # For the purposes of generating COinS metadata, LDR/06-07 can be
564 # considered the same for UNIMARC and MARC21
573 'i' => 'audioRecording',
574 'j' => 'audioRecording',
577 'm' => 'computerProgram',
582 'a' => 'journalArticle',
586 $genre = $fmts6->{$pos6} ?
$fmts6->{$pos6} : 'book';
588 if ( $genre eq 'book' ) {
589 $genre = $fmts7->{$pos7} if $fmts7->{$pos7};
592 ##### We must transform mtx to a valable mtx and document type ####
593 if ( $genre eq 'book' ) {
596 } elsif ( $genre eq 'journal' ) {
599 } elsif ( $genre eq 'journalArticle' ) {
607 if ( C4
::Context
->preference("marcflavour") eq "UNIMARC" ) {
610 $aulast = $record->subfield( '700', 'a' ) || '';
611 $aufirst = $record->subfield( '700', 'b' ) || '';
612 push @authors, "$aufirst $aulast" if ($aufirst or $aulast);
615 if ( $record->field('200') ) {
616 for my $au ( $record->field('200')->subfield('g') ) {
621 $title = $record->subfield( '200', 'a' );
622 my $subfield_210d = $record->subfield('210', 'd');
623 if ($subfield_210d and $subfield_210d =~ /(\d{4})/) {
626 $publisher = $record->subfield( '210', 'c' ) || '';
627 $isbn = $record->subfield( '010', 'a' ) || '';
628 $issn = $record->subfield( '011', 'a' ) || '';
631 # MARC21 need some improve
634 if ( $record->field('100') ) {
635 push @authors, $record->subfield( '100', 'a' );
639 if ( $record->field('700') ) {
640 for my $au ( $record->field('700')->subfield('a') ) {
644 $title = $record->field('245')->as_string('ab');
645 if ($titletype eq 'a') {
646 $pubyear = $record->field('008') || '';
647 $pubyear = substr($pubyear->data(), 7, 4) if $pubyear;
648 $isbn = $record->subfield( '773', 'z' ) || '';
649 $issn = $record->subfield( '773', 'x' ) || '';
650 $hosttitle = $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{};
651 my @rels = $record->subfield( '773', 'g' );
652 $pages = join(', ', @rels);
654 $pubyear = $record->subfield( '260', 'c' ) || '';
655 $publisher = $record->subfield( '260', 'b' ) || '';
656 $isbn = $record->subfield( '020', 'a' ) || '';
657 $issn = $record->subfield( '022', 'a' ) || '';
663 [ 'ctx_ver', 'Z39.88-2004' ],
664 [ 'rft_val_fmt', "info:ofi/fmt:kev:mtx:$mtx" ],
665 [ ($mtx eq 'dc' ?
'rft.type' : 'rft.genre'), $genre ],
666 [ "rft.${titletype}title", $title ],
669 # rft.title is authorized only once, so by checking $titletype
670 # we ensure that rft.title is not already in the list.
671 if ($hosttitle and $titletype) {
672 push @params, [ 'rft.title', $hosttitle ];
676 [ 'rft.isbn', $isbn ],
677 [ 'rft.issn', $issn ],
680 # If it's a subscription, these informations have no meaning.
681 if ($genre ne 'journal') {
683 [ 'rft.aulast', $aulast ],
684 [ 'rft.aufirst', $aufirst ],
685 (map { [ 'rft.au', $_ ] } @authors),
686 [ 'rft.pub', $publisher ],
687 [ 'rft.date', $pubyear ],
688 [ 'rft.pages', $pages ],
692 my $coins_value = join( '&',
693 map { $$_[1] ?
$$_[0] . '=' . uri_escape_utf8
( $$_[1] ) : () } @params );
700 my $url = $biblio->get_openurl;
702 Returns url for OpenURL resolver set in OpenURLResolverURL system preference
709 my $OpenURLResolverURL = C4
::Context
->preference('OpenURLResolverURL');
711 if ($OpenURLResolverURL) {
712 my $uri = URI
->new($OpenURLResolverURL);
714 if (not defined $uri->query) {
715 $OpenURLResolverURL .= '?';
717 $OpenURLResolverURL .= '&';
719 $OpenURLResolverURL .= $self->get_coins;
722 return $OpenURLResolverURL;
727 my $serial = $biblio->is_serial
729 Return boolean true if this bibbliographic record is continuing resource
736 return 1 if $self->serial;
738 my $record = $self->metadata->record;
739 return 1 if substr($record->leader, 7, 1) eq 's';
744 =head3 custom_cover_image_url
746 my $image_url = $biblio->custom_cover_image_url
748 Return the specific url of the cover image for this bibliographic record.
749 It is built regaring the value of the system preference CustomCoverImagesURL
753 sub custom_cover_image_url
{
755 my $url = C4
::Context
->preference('CustomCoverImagesURL');
756 if ( $url =~ m
|{isbn
}| ) {
757 my $isbn = $self->biblioitem->isbn;
758 $url =~ s
|{isbn
}|$isbn|g
;
760 if ( $url =~ m
|{normalized_isbn
}| ) {
761 my $normalized_isbn = C4
::Koha
::GetNormalizedISBN
($self->biblioitem->isbn);
762 $url =~ s
|{normalized_isbn
}|$normalized_isbn|g
;
764 if ( $url =~ m
|{issn
}| ) {
765 my $issn = $self->biblioitem->issn;
766 $url =~ s
|{issn
}|$issn|g
;
769 my $re = qr
|{(?
<field
>\d
{3})\
$(?
<subfield
>.)}|;
771 my $field = $+{field
};
772 my $subfield = $+{subfield
};
773 my $marc_record = $self->metadata->record;
774 my $value = $marc_record->subfield($field, $subfield);
775 $url =~ s
|$re|$value|;
783 my $json = $biblio->to_api;
785 Overloaded method that returns a JSON representation of the Koha::Biblio object,
786 suitable for API output. The related Koha::Biblioitem object is merged as expected
792 my ($self, $args) = @_;
794 my $response = $self->SUPER::to_api
( $args );
795 my $biblioitem = $self->biblioitem->to_api;
797 return { %$response, %$biblioitem };
800 =head3 to_api_mapping
802 This method returns the mapping for representing a Koha::Biblio object
809 biblionumber
=> 'biblio_id',
810 frameworkcode
=> 'framework_id',
811 unititle
=> 'uniform_title',
812 seriestitle
=> 'series_title',
813 copyrightdate
=> 'copyright_date',
814 datecreated
=> 'creation_date'
818 =head2 Internal methods
830 Kyle M Hall <kyle@bywatersolutions.com>