From d5a511f793de5d76cae0ba7c869d60a2f84edeb3 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Tue, 19 Oct 2021 11:40:45 +0100 Subject: [PATCH] Bug 29268: Correct cni handling for get_marc_host This patch adds a new method for fetching an ordered list of search queries that can then be used to fetch a records host biblio. --- Koha/Biblio.pm | 146 +++++++++++++++++------ t/db_dependent/Koha/Biblio.t | 33 ++++- t/db_dependent/Koha/Biblio/host_record.t | 1 + 3 files changed, 144 insertions(+), 36 deletions(-) diff --git a/Koha/Biblio.pm b/Koha/Biblio.pm index c4c12128af..aa0c3325b4 100644 --- a/Koha/Biblio.pm +++ b/Koha/Biblio.pm @@ -1019,53 +1019,129 @@ sub to_api_mapping { =cut sub get_marc_host { - my ($self, $params) = @_; + my ( $self, $params ) = @_; my $no_items = $params->{no_items}; - return if C4::Context->preference('marcflavour') eq 'UNIMARC'; # TODO + return if C4::Context->preference('marcflavour') eq 'UNIMARC'; # TODO return if $params->{no_items} && $self->items->count > 0; - my $record; - eval { $record = $self->metadata->record }; - return if !$record; + my ( $bibno, $hostfld ); + my $engine = Koha::SearchEngine::Search->new( + { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); - # We pick the first $w with your MARCOrgCode or the first $w that has no - # code (between parentheses) at all. - my $orgcode = C4::Context->preference('MARCOrgCode') // q{}; - my $hostfld; - foreach my $f ( $record->field('773') ) { - my $w = $f->subfield('w') or next; - if( $w =~ /^\($orgcode\)\s*(\d+)/i or $w =~ /^\d+/ ) { - $hostfld = $f; - last; - } - } - 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 ) { - $bibno = $engine->extract_biblionumber( $results->[0] ); - last; - } - # Add or remove orgcode for second try - if( $try == 1 && $rcn =~ /\)\s*(\d+)/ ) { - $rcn = $1; # number only - } elsif( $try == 1 && $rcn =~ /^\d+/ ) { - $rcn = "($orgcode)$rcn"; - } else { + # Search for host with/without orgcode + my $queries = $self->get_host_queries; + for my $query ( @{$queries} ) { + my ( $error, $results, $total_hits ) = + $engine->simple_search_compat( $query->{query}, 0, 1 ); + + if ( !$error and $total_hits == 1 ) { + $bibno = $engine->extract_biblionumber( $results->[0] ); + $hostfld = $query->{field}; last; } } - if( $bibno ) { + + if ($bibno) { my $host = Koha::Biblios->find($bibno) or return; return wantarray ? ( $host, $hostfld->subfield('g') ) : $host; } } +=head2 get_host_queries + +Returns an array of queries which can be used to search for the host of this MARC21 biblio. + +The array is in most specific to least specific search order. + +The algorithm used to construct the search query is: +1. Iterate through all `773` fields. +2. If using 'UseControlNumber' iterate through each `w` subfield. +2a. Best match is where the $w contains `(cni)cn` and the cni matches our orgcode. +2b. Next best match is where $w contains `(cni)cn` and the cni does not match our orgcode. +2c. Subsequent matches are where $w contains just `cn` +3. If not using 'UseControlNumber' we search on title and author + +=cut + +sub get_host_queries { + my ($self) = @_; + + my $record = $self->metadata->record; + + my $builder = Koha::SearchEngine::QueryBuilder->new( + { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); + my $cni_queries = []; + my $cn_queries = []; + my $ti_queries = []; + + my $orgcode = C4::Context->preference('MARCOrgCode') // q{}; + foreach my $f ( $record->field('773') ) { + if ( C4::Context->preference('UseControlNumber') ) { + foreach my $w ( $f->subfield('w') ) { + + # Control-number-identifier prepended + if ( $w =~ /^\((?.*)\)\s*(?\d+)/i ) { + + # Local orgcode match + if ( $+{cni} eq $orgcode ) { + my $query = + "(Control-number:" + . $+{cn} + . " AND cni:" + . $+{cni} . ")"; + unshift( + @{$cni_queries}, + { query => $query, field => $f } + ); + } + + # Other orgcodes + else { + my $query = + "(Control-number:" + . $+{cn} + . " AND cni:" + . $+{cni} . ")"; + push( + @{$cni_queries}, + { + query => $query, + field => $f + } + ); + } + } + + # Control-number only + elsif ( $w =~ /^(?\d+)/ ) { + my $query = "(Control-number:" . $+{cn} . ")"; + push( + @{$cn_queries}, + { + query => $query, + field => $f + } + ); + } + } + } + else { + my $searchstr; + my $cleaned_title = $f->subfield('t'); + $cleaned_title =~ tr|/||; + $cleaned_title = $builder->clean_search_term($cleaned_title); + $searchstr = "ti,phr:($cleaned_title)"; + my $cleaned_author = $f->subfield('a'); + $cleaned_author =~ tr|/||; + $cleaned_author = $builder->clean_search_term($cleaned_author); + $searchstr .= " AND au:($cleaned_author)" if $cleaned_author; + push( @{$ti_queries}, { query => $searchstr, field => $f } ); + } + } + + return [ @{$cni_queries}, @{$cn_queries}, @{$ti_queries} ]; +} + =head2 Internal methods =head3 type diff --git a/t/db_dependent/Koha/Biblio.t b/t/db_dependent/Koha/Biblio.t index 73dcb2ab13..44de5913e6 100755 --- a/t/db_dependent/Koha/Biblio.t +++ b/t/db_dependent/Koha/Biblio.t @@ -17,7 +17,7 @@ use Modern::Perl; -use Test::More tests => 19; +use Test::More tests => 20; use Test::Warn; use C4::Biblio qw( AddBiblio ModBiblio ModBiblioMarc ); @@ -564,6 +564,37 @@ subtest 'get_marc_components() tests' => sub { $schema->storage->txn_rollback; }; +subtest 'get_host_queries' => sub { + plan tests => 4; + + my $biblio = $builder->build_sample_biblio(); + my $biblionumber = $biblio->biblionumber; + my $record = $biblio->metadata->record; + + my $marc_773_field = MARC::Field->new('773', '0', '#', 'a' => 'J. K Rowling', 't' => 'The Tales of Beedle the Bard', 'w' => '12345' ); + $record->append_fields($marc_773_field); + C4::Biblio::ModBiblio( $record, $biblio->biblionumber ); + $biblio = Koha::Biblios->find( $biblio->biblionumber); + + t::lib::Mocks::mock_preference( 'UseControlNumber', '0' ); + my $queries = $biblio->get_host_queries; + is($queries->[0]->{query}, "ti,phr:(The Tales of Beedle the Bard) AND au:(J. K Rowling)", "UseControlNumber disabled"); + + t::lib::Mocks::mock_preference( 'UseControlNumber', '1' ); + + $queries = $biblio->get_host_queries; + is($queries->[0]->{query}, "(Control-number:12345)", "UseControlNumber enabled without MarcOrgCode"); + + $marc_773_field = MARC::Field->new('773', '0', '#', 'a' => 'J. K Rowling', 't' => 'The Tales of Beedle the Bard', 'w' => '(OSt)23456' ); + $record->append_fields($marc_773_field); + C4::Biblio::ModBiblio( $record, $biblio->biblionumber ); + $biblio = Koha::Biblios->find( $biblio->biblionumber); + + $queries = $biblio->get_host_queries; + is($queries->[0]->{query}, "(Control-number:23456 AND cni:OSt)", "UseControlNumber enabled with MarcOrgCode comes out first"); + is($queries->[1]->{query}, "(Control-number:12345)", "UseControlNumber enabled without MarcOrgCode comes out second"); +}; + subtest 'get_components_query' => sub { plan tests => 3; diff --git a/t/db_dependent/Koha/Biblio/host_record.t b/t/db_dependent/Koha/Biblio/host_record.t index 505efe0b95..48ed75ef4a 100755 --- a/t/db_dependent/Koha/Biblio/host_record.t +++ b/t/db_dependent/Koha/Biblio/host_record.t @@ -40,6 +40,7 @@ subtest 'get_marc_host' => sub { t::lib::Mocks::mock_preference( 'marcflavour', 'MARC21' ); t::lib::Mocks::mock_preference( 'MARCOrgCode', 'xyz' ); + t::lib::Mocks::mock_preference( 'UseControlNumber', '1' ); my $bib1 = $builder->build_object({ class => 'Koha::Biblios' }); my $bib2 = $builder->build_object({ class => 'Koha::Biblios' }); -- 2.20.1