Bug 32824: Fix cataloguing/value_builder/unimarc_field_100.pl
[koha-ffzg.git] / Koha / Biblio.pm
index 0e8e477..5bf369c 100644 (file)
@@ -24,7 +24,6 @@ use URI;
 use URI::Escape qw( uri_escape_utf8 );
 
 use C4::Koha qw( GetNormalizedISBN );
 use URI::Escape qw( uri_escape_utf8 );
 
 use C4::Koha qw( GetNormalizedISBN );
-use C4::XSLT qw( transformMARCXML4XSLT );
 
 use Koha::Database;
 use Koha::DateUtils qw( dt_from_string );
 
 use Koha::Database;
 use Koha::DateUtils qw( dt_from_string );
@@ -34,6 +33,7 @@ use base qw(Koha::Object);
 use Koha::Acquisition::Orders;
 use Koha::ArticleRequests;
 use Koha::Biblio::Metadatas;
 use Koha::Acquisition::Orders;
 use Koha::ArticleRequests;
 use Koha::Biblio::Metadatas;
+use Koha::Biblio::ItemGroups;
 use Koha::Biblioitems;
 use Koha::Checkouts;
 use Koha::CirculationRules;
 use Koha::Biblioitems;
 use Koha::Checkouts;
 use Koha::CirculationRules;
@@ -41,6 +41,8 @@ use Koha::Item::Transfer::Limits;
 use Koha::Items;
 use Koha::Libraries;
 use Koha::Old::Checkouts;
 use Koha::Items;
 use Koha::Libraries;
 use Koha::Old::Checkouts;
+use Koha::Recalls;
+use Koha::RecordProcessor;
 use Koha::Suggestions;
 use Koha::Subscriptions;
 use Koha::SearchEngine;
 use Koha::Suggestions;
 use Koha::Subscriptions;
 use Koha::SearchEngine;
@@ -117,6 +119,21 @@ sub active_orders {
     return $self->orders->search({ datecancellationprinted => undef });
 }
 
     return $self->orders->search({ datecancellationprinted => undef });
 }
 
+=head3 item_groups
+
+my $item_groups = $biblio->item_groups();
+
+Returns a Koha::Biblio::ItemGroups object
+
+=cut
+
+sub item_groups {
+    my ( $self ) = @_;
+
+    my $item_groups = $self->_result->item_groups;
+    return Koha::Biblio::ItemGroups->_new_from_dbic($item_groups);
+}
+
 =head3 can_article_request
 
 my $bool = $biblio->can_article_request( $borrower );
 =head3 can_article_request
 
 my $bool = $biblio->can_article_request( $borrower );
@@ -515,8 +532,8 @@ sub suggestions {
 
   my $components = $self->get_marc_components();
 
 
   my $components = $self->get_marc_components();
 
-Returns an array of MARCXML data, which are component parts of
-this object (MARC21 773$w points to this)
+Returns an array of search results data, which are component parts of
+this object (MARC21 773 points to this)
 
 =cut
 
 
 =cut
 
@@ -525,19 +542,19 @@ sub get_marc_components {
 
     return [] if (C4::Context->preference('marcflavour') ne 'MARC21');
 
 
     return [] if (C4::Context->preference('marcflavour') ne 'MARC21');
 
-    my $searchstr = $self->get_components_query;
+    my ( $searchstr, $sort ) = $self->get_components_query;
 
     my $components;
     if (defined($searchstr)) {
         my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
 
     my $components;
     if (defined($searchstr)) {
         my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
-        my ( $error, $results, $total_hits );
+        my ( $error, $results, $facets );
         eval {
         eval {
-            ( $error, $results, $total_hits ) = $searcher->simple_search_compat( $searchstr, 0, $max_results );
+            ( $error, $results, $facets ) = $searcher->search_compat( $searchstr, undef, [$sort], ['biblioserver'], $max_results, 0, undef, undef, 'ccl', 0 );
         };
         if( $error || $@ ) {
             $error //= q{};
             $error .= $@ if $@;
         };
         if( $error || $@ ) {
             $error //= q{};
             $error .= $@ if $@;
-            warn "Warning from simple_search_compat: '$error'";
+            warn "Warning from search_compat: '$error'";
             $self->add_message(
                 {
                     type    => 'error',
             $self->add_message(
                 {
                     type    => 'error',
@@ -546,7 +563,7 @@ sub get_marc_components {
                 }
             );
         }
                 }
             );
         }
-        $components = $results if defined($results) && @$results;
+        $components = $results->{biblioserver}->{RECORDS} if defined($results) && $results->{biblioserver}->{hits};
     }
 
     return $components // [];
     }
 
     return $components // [];
@@ -564,6 +581,9 @@ sub get_components_query {
     my $builder = Koha::SearchEngine::QueryBuilder->new(
         { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
     my $marc = $self->metadata->record;
     my $builder = Koha::SearchEngine::QueryBuilder->new(
         { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
     my $marc = $self->metadata->record;
+    my $component_sort_field = C4::Context->preference('ComponentSortField') // "title";
+    my $component_sort_order = C4::Context->preference('ComponentSortOrder') // "asc";
+    my $sort = $component_sort_field . "_" . $component_sort_order;
 
     my $searchstr;
     if ( C4::Context->preference('UseControlNumber') ) {
 
     my $searchstr;
     if ( C4::Context->preference('UseControlNumber') ) {
@@ -575,12 +595,12 @@ sub get_components_query {
 
             if ( !defined($pf003) ) {
                 # search for 773$w='Host001'
 
             if ( !defined($pf003) ) {
                 # search for 773$w='Host001'
-                $searchstr .= "rcn:" . $pf001->data();
+                $searchstr .= "rcn:\"" . $pf001->data()."\"";
             }
             else {
                 $searchstr .= "(";
                 # search for (773$w='Host001' and 003='Host003') or 773$w='(Host003)Host001'
             }
             else {
                 $searchstr .= "(";
                 # search for (773$w='Host001' and 003='Host003') or 773$w='(Host003)Host001'
-                $searchstr .= "(rcn:" . $pf001->data() . " AND cni:" . $pf003->data() . ")";
+                $searchstr .= "(rcn:\"" . $pf001->data() . "\" AND cni:\"" . $pf003->data() . "\")";
                 $searchstr .= " OR rcn:\"" . $pf003->data() . " " . $pf001->data() . "\"";
                 $searchstr .= ")";
             }
                 $searchstr .= " OR rcn:\"" . $pf003->data() . " " . $pf001->data() . "\"";
                 $searchstr .= ")";
             }
@@ -594,10 +614,15 @@ sub get_components_query {
         my $cleaned_title = $marc->subfield('245', "a");
         $cleaned_title =~ tr|/||;
         $cleaned_title = $builder->clean_search_term($cleaned_title);
         my $cleaned_title = $marc->subfield('245', "a");
         $cleaned_title =~ tr|/||;
         $cleaned_title = $builder->clean_search_term($cleaned_title);
-        $searchstr = "Host-item:($cleaned_title)";
+        $searchstr = qq#Host-item:("$cleaned_title")#;
+    }
+    my ($error, $query ,$query_str) = $builder->build_query_compat( undef, [$searchstr], undef, undef, [$sort], 0 );
+    if( $error ){
+        warn $error;
+        return;
     }
 
     }
 
-    return $searchstr;
+    return ($query, $query_str, $sort);
 }
 
 =head3 subscriptions
 }
 
 =head3 subscriptions
@@ -912,7 +937,7 @@ sub cover_images {
 
 =head3 get_marc_notes
 
 
 =head3 get_marc_notes
 
-    $marcnotesarray = $biblio->get_marc_notes({ marcflavour => $marcflavour });
+    $marcnotesarray = $biblio->get_marc_notes({ opac => 1 });
 
 Get all notes from the MARC record and returns them in an array.
 The notes are stored in different fields depending on MARC flavour.
 
 Get all notes from the MARC record and returns them in an array.
 The notes are stored in different fields depending on MARC flavour.
@@ -923,12 +948,23 @@ MARC21 5XX $u subfields receive special attention as they are URIs.
 sub get_marc_notes {
     my ( $self, $params ) = @_;
 
 sub get_marc_notes {
     my ( $self, $params ) = @_;
 
-    my $marcflavour = $params->{marcflavour};
-    my $opac = $params->{opac};
+    my $marcflavour = C4::Context->preference('marcflavour');
+    my $opac = $params->{opac} // '0';
+    my $interface = $params->{opac} ? 'opac' : 'intranet';
 
 
-    my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
-    my @marcnotes;
+    my $record = $params->{record} // $self->metadata->record;
+    my $record_processor = Koha::RecordProcessor->new(
+        {
+            filters => [ 'ViewPolicy', 'ExpandCodedFields' ],
+            options => {
+                interface     => $interface,
+                frameworkcode => $self->frameworkcode
+            }
+        }
+    );
+    $record_processor->process($record);
 
 
+    my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
     #MARC21 specs indicate some notes should be private if first indicator 0
     my %maybe_private = (
         541 => 1,
     #MARC21 specs indicate some notes should be private if first indicator 0
     my %maybe_private = (
         541 => 1,
@@ -940,9 +976,8 @@ sub get_marc_notes {
 
     my %hiddenlist = map { $_ => 1 }
         split( /,/, C4::Context->preference('NotesToHide'));
 
     my %hiddenlist = map { $_ => 1 }
         split( /,/, C4::Context->preference('NotesToHide'));
-    my $record = $self->metadata->record;
-    $record = transformMARCXML4XSLT( $self->biblionumber, $record, $opac );
 
 
+    my @marcnotes;
     foreach my $field ( $record->field($scope) ) {
         my $tag = $field->tag();
         next if $hiddenlist{ $tag };
     foreach my $field ( $record->field($scope) ) {
         my $tag = $field->tag();
         next if $hiddenlist{ $tag };
@@ -966,39 +1001,35 @@ sub get_marc_notes {
     return \@marcnotes;
 }
 
     return \@marcnotes;
 }
 
-=head3 get_marc_authors
-
-    my $authors = $biblio->get_marc_authors;
+=head3 _get_marc_authors
 
 
-Get all authors from the MARC record and returns them in an array.
-The authors are stored in different fields depending on MARC flavour
+Private method to return the list of authors contained in the MARC record.
+See get get_marc_contributors and get_marc_authors for the public methods.
 
 =cut
 
 
 =cut
 
-sub get_marc_authors {
+sub _get_marc_authors {
     my ( $self, $params ) = @_;
 
     my ( $self, $params ) = @_;
 
-    my ( $mintag, $maxtag, $fields_filter );
-    my $marcflavour = C4::Context->preference('marcflavour');
+    my $fields_filter = $params->{fields_filter};
+    my $mintag        = $params->{mintag};
+    my $maxtag        = $params->{maxtag};
+
+    my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
+    my $marcflavour        = C4::Context->preference('marcflavour');
 
     # tagslib useful only for UNIMARC author responsibilities
 
     # tagslib useful only for UNIMARC author responsibilities
-    my $tagslib;
-    if ( $marcflavour eq "UNIMARC" ) {
-        $tagslib = C4::Biblio::GetMarcStructure( 1, $self->frameworkcode, { unsafe => 1 });
-        $mintag = "700";
-        $maxtag = "712";
-        $fields_filter = '7..';
-    } else { # marc21/normarc
-        $mintag = "700";
-        $maxtag = "720";
-        $fields_filter = '7..';
-    }
+    my $tagslib = $marcflavour eq "UNIMARC"
+      ? C4::Biblio::GetMarcStructure( 1, $self->frameworkcode, { unsafe => 1 } )
+      : undef;
 
     my @marcauthors;
 
     my @marcauthors;
-    my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
-
     foreach my $field ( $self->metadata->record->field($fields_filter) ) {
     foreach my $field ( $self->metadata->record->field($fields_filter) ) {
-        next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
+
+        next
+          if $mintag && $field->tag() < $mintag
+          || $maxtag && $field->tag() > $maxtag;
+
         my @subfields_loop;
         my @link_loop;
         my @subfields  = $field->subfields();
         my @subfields_loop;
         my @link_loop;
         my @subfields  = $field->subfields();
@@ -1062,6 +1093,76 @@ sub get_marc_authors {
     return \@marcauthors;
 }
 
     return \@marcauthors;
 }
 
+=head3 get_marc_contributors
+
+    my $contributors = $biblio->get_marc_contributors;
+
+Get all contributors (but first author) from the MARC record and returns them in an array.
+They are stored in different fields depending on MARC flavour (700..720 for MARC21)
+
+=cut
+
+sub get_marc_contributors {
+    my ( $self, $params ) = @_;
+
+    my ( $mintag, $maxtag, $fields_filter );
+    my $marcflavour = C4::Context->preference('marcflavour');
+
+    if ( $marcflavour eq "UNIMARC" ) {
+        $mintag = "700";
+        $maxtag = "712";
+        $fields_filter = '7..';
+    } else { # marc21/normarc
+        $mintag = "700";
+        $maxtag = "720";
+        $fields_filter = '7..';
+    }
+
+    return $self->_get_marc_authors(
+        {
+            fields_filter => $fields_filter,
+            mintag       => $mintag,
+            maxtag       => $maxtag
+        }
+    );
+}
+
+=head3 get_marc_authors
+
+    my $authors = $biblio->get_marc_authors;
+
+Get all authors from the MARC record and returns them in an array.
+They are stored in different fields depending on MARC flavour
+(main author from 100 then secondary authors from 700..720).
+
+=cut
+
+sub get_marc_authors {
+    my ( $self, $params ) = @_;
+
+    my ( $mintag, $maxtag, $fields_filter );
+    my $marcflavour = C4::Context->preference('marcflavour');
+
+    if ( $marcflavour eq "UNIMARC" ) {
+        $fields_filter = '200';
+    } else { # marc21/normarc
+        $fields_filter = '100';
+    }
+
+    my @first_authors = @{$self->_get_marc_authors(
+        {
+            fields_filter => $fields_filter,
+            mintag       => $mintag,
+            maxtag       => $maxtag
+        }
+    )};
+
+    my @other_authors = @{$self->get_marc_contributors};
+
+    return [@first_authors, @other_authors];
+}
+
+
 =head3 to_api
 
     my $json = $biblio->to_api;
 =head3 to_api
 
     my $json = $biblio->to_api;
@@ -1095,7 +1196,8 @@ sub to_api_mapping {
         unititle         => 'uniform_title',
         seriestitle      => 'series_title',
         copyrightdate    => 'copyright_date',
         unititle         => 'uniform_title',
         seriestitle      => 'series_title',
         copyrightdate    => 'copyright_date',
-        datecreated      => 'creation_date'
+        datecreated      => 'creation_date',
+        deleted_on       => undef,
     };
 }
 
     };
 }
 
@@ -1103,7 +1205,7 @@ sub to_api_mapping {
 
     $host = $biblio->get_marc_host;
     # OR:
 
     $host = $biblio->get_marc_host;
     # OR:
-    ( $host, $relatedparts ) = $biblio->get_marc_host;
+    ( $host, $relatedparts, $hostinfo ) = $biblio->get_marc_host;
 
     Returns host biblio record from MARC21 773 (undef if no 773 present).
     It looks at the first 773 field with MARCorgCode or only a control
 
     Returns host biblio record from MARC21 773 (undef if no 773 present).
     It looks at the first 773 field with MARCorgCode or only a control
@@ -1112,6 +1214,12 @@ sub to_api_mapping {
     If there are, the sub returns undef.
     Called in list context, it also returns 773$g (related parts).
 
     If there are, the sub returns undef.
     Called in list context, it also returns 773$g (related parts).
 
+    If there is no $w, we use $0 (host biblionumber) or $9 (host itemnumber)
+    to search for the host record. If there is also no $0 and no $9, we search
+    using author and title. Failing all of that, we return an undef host and
+    form a concatenation of strings with 773$agt for host information,
+    returned when called in list context.
+
 =cut
 
 sub get_marc_host {
 =cut
 
 sub get_marc_host {
@@ -1135,12 +1243,35 @@ sub get_marc_host {
             last;
         }
     }
             last;
         }
     }
+
+    my $engine = Koha::SearchEngine::Search->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+    my $bibno;
+    if ( !$hostfld and $record->subfield('773','t') ) {
+        # not linked using $w
+        my $unlinkedf = $record->field('773');
+        my $host;
+        if ( C4::Context->preference("EasyAnalyticalRecords") ) {
+            if ( $unlinkedf->subfield('0') ) {
+                # use 773$0 host biblionumber
+                $bibno = $unlinkedf->subfield('0');
+            } elsif ( $unlinkedf->subfield('9') ) {
+                # use 773$9 host itemnumber
+                my $linkeditemnumber = $unlinkedf->subfield('9');
+                $bibno = Koha::Items->find( $linkeditemnumber )->biblionumber;
+            }
+        }
+        if ( $bibno ) {
+            my $host = Koha::Biblios->find($bibno) or return;
+            return wantarray ? ( $host, $unlinkedf->subfield('g') ) : $host;
+        }
+        # just return plaintext and no host record
+        my $hostinfo = join( ", ", $unlinkedf->subfield('a'), $unlinkedf->subfield('t'), $unlinkedf->subfield('g') );
+        return wantarray ? ( undef, $unlinkedf->subfield('g'), $hostinfo ) : undef;
+    }
     return if !$hostfld;
     my $rcn = $hostfld->subfield('w');
 
     # Look for control number with/without orgcode
     return if !$hostfld;
     my $rcn = $hostfld->subfield('w');
 
     # Look for control number with/without orgcode
-    my $engine = Koha::SearchEngine::Search->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
-    my $bibno;
     for my $try (1..2) {
         my ( $error, $results, $total_hits ) = $engine->simple_search_compat( 'Control-number='.$rcn, 0,1 );
         if( !$error and $total_hits == 1 ) {
     for my $try (1..2) {
         my ( $error, $results, $total_hits ) = $engine->simple_search_compat( 'Control-number='.$rcn, 0,1 );
         if( !$error and $total_hits == 1 ) {
@@ -1249,7 +1380,7 @@ sub can_be_recalled {
         return 0 if ( $patron->recalls->filter_by_current->count >= $recalls_allowed );
 
         # check borrower has not reached open recalls allowed per record limit
         return 0 if ( $patron->recalls->filter_by_current->count >= $recalls_allowed );
 
         # check borrower has not reached open recalls allowed per record limit
-        return 0 if ( $patron->recalls->filter_by_current->search({ biblionumber => $self->biblionumber })->count >= $recalls_per_record );
+        return 0 if ( $patron->recalls->filter_by_current->search({ biblio_id => $self->biblionumber })->count >= $recalls_per_record );
 
         # check if any of the items under this biblio are already checked out by this borrower
         return 0 if ( Koha::Checkouts->search({ itemnumber => [ @all_itemnumbers ], borrowernumber => $patron->borrowernumber })->count > 0 );
 
         # check if any of the items under this biblio are already checked out by this borrower
         return 0 if ( Koha::Checkouts->search({ itemnumber => [ @all_itemnumbers ], borrowernumber => $patron->borrowernumber })->count > 0 );