Bug 29697: Replace GetMarcBiblio occurrences with $biblio->metadata->record
[srvgit] / C4 / Biblio.pm
index 3e03c52..491201a 100644 (file)
@@ -35,7 +35,6 @@ BEGIN {
         GetMarcISBN
         GetMarcISSN
         GetMarcSubjects
         GetMarcISBN
         GetMarcISSN
         GetMarcSubjects
-        GetMarcAuthors
         GetMarcSeries
         GetMarcUrls
         GetUsedMarcStructure
         GetMarcSeries
         GetMarcUrls
         GetUsedMarcStructure
@@ -61,11 +60,11 @@ BEGIN {
         DelBiblio
         BiblioAutoLink
         LinkBibHeadingsToAuthorities
         DelBiblio
         BiblioAutoLink
         LinkBibHeadingsToAuthorities
+        ApplyMarcOverlayRules
         TransformMarcToKoha
         TransformHtmlToMarc
         TransformHtmlToXml
         prepare_host_field
         TransformMarcToKoha
         TransformHtmlToMarc
         TransformHtmlToXml
         prepare_host_field
-        TransformMarcToKohaOneField
     );
 
     # Internal functions
     );
 
     # Internal functions
@@ -106,9 +105,11 @@ use Koha::Logger;
 use Koha::Caches;
 use Koha::Authority::Types;
 use Koha::Acquisition::Currencies;
 use Koha::Caches;
 use Koha::Authority::Types;
 use Koha::Acquisition::Currencies;
+use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
 use Koha::Biblio::Metadatas;
 use Koha::Holds;
 use Koha::ItemTypes;
 use Koha::Biblio::Metadatas;
 use Koha::Holds;
 use Koha::ItemTypes;
+use Koha::MarcOverlayRules;
 use Koha::Plugins;
 use Koha::SearchEngine;
 use Koha::SearchEngine::Indexer;
 use Koha::Plugins;
 use Koha::SearchEngine;
 use Koha::SearchEngine::Indexer;
@@ -185,31 +186,29 @@ The first argument is a C<MARC::Record> object containing the
 bib to add, while the second argument is the desired MARC
 framework code.
 
 bib to add, while the second argument is the desired MARC
 framework code.
 
-This function also accepts a third, optional argument: a hashref
-to additional options.  The only defined option is C<defer_marc_save>,
-which if present and mapped to a true value, causes C<AddBiblio>
-to omit the call to save the MARC in C<biblio_metadata.metadata>
-This option is provided B<only>
-for the use of scripts such as C<bulkmarcimport.pl> that may need
-to do some manipulation of the MARC record for item parsing before
-saving it and which cannot afford the performance hit of saving
-the MARC record twice.  Consequently, do not use that option
-unless you can guarantee that C<ModBiblioMarc> will be called.
+The C<$options> argument is a hashref with additional parameters:
+
+=over 4
+
+=item B<defer_marc_save>: used when ModBiblioMarc is handled by the caller
+
+=item B<skip_record_index>: used when the indexing schedulling will be handled by the caller
+
+=back
 
 =cut
 
 sub AddBiblio {
 
 =cut
 
 sub AddBiblio {
-    my $record          = shift;
-    my $frameworkcode   = shift;
-    my $options         = @_ ? shift : undef;
-    my $defer_marc_save = 0;
+    my ( $record, $frameworkcode, $options ) = @_;
+
+    $options //= {};
+    my $skip_record_index = $options->{skip_record_index} || 0;
+    my $defer_marc_save   = $options->{defer_marc_save}   || 0;
+
     if (!$record) {
         carp('AddBiblio called with undefined record');
         return;
     }
     if (!$record) {
         carp('AddBiblio called with undefined record');
         return;
     }
-    if ( defined $options and exists $options->{'defer_marc_save'} and $options->{'defer_marc_save'} ) {
-        $defer_marc_save = 1;
-    }
 
     my $schema = Koha::Database->schema;
     my ( $biblionumber, $biblioitemnumber );
 
     my $schema = Koha::Database->schema;
     my ( $biblionumber, $biblioitemnumber );
@@ -218,7 +217,7 @@ sub AddBiblio {
 
             # transform the data into koha-table style data
             SetUTF8Flag($record);
 
             # transform the data into koha-table style data
             SetUTF8Flag($record);
-            my $olddata = TransformMarcToKoha( $record, $frameworkcode );
+            my $olddata = TransformMarcToKoha({ record => $record, limit_table => 'no_items' });
 
             my $biblio = Koha::Biblio->new(
                 {
 
             my $biblio = Koha::Biblio->new(
                 {
@@ -290,7 +289,7 @@ sub AddBiblio {
             }
 
             # now add the record
             }
 
             # now add the record
-            ModBiblioMarc( $record, $biblionumber ) unless $defer_marc_save;
+            ModBiblioMarc( $record, $biblionumber, { skip_record_index => $skip_record_index } ) unless $defer_marc_save;
 
             # update OAI-PMH sets
             if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
 
             # update OAI-PMH sets
             if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
@@ -310,7 +309,7 @@ sub AddBiblio {
 
 =head2 ModBiblio
 
 
 =head2 ModBiblio
 
-  ModBiblio( $record,$biblionumber,$frameworkcode, $disable_autolink);
+  ModBiblio($record, $biblionumber, $frameworkcode, $options);
 
 Replace an existing bib record identified by C<$biblionumber>
 with one supplied by the MARC::Record object C<$record>.  The embedded
 
 Replace an existing bib record identified by C<$biblionumber>
 with one supplied by the MARC::Record object C<$record>.  The embedded
@@ -326,27 +325,50 @@ in the C<biblio> and C<biblioitems> tables, as well as
 which fields are used to store embedded item, biblioitem,
 and biblionumber data for indexing.
 
 which fields are used to store embedded item, biblioitem,
 and biblionumber data for indexing.
 
-Unless C<$disable_autolink> is passed ModBiblio will relink record headings
+The C<$options> argument is a hashref with additional parameters:
+
+=over 4
+
+=item C<overlay_context>
+
+This parameter is forwarded to L</ApplyMarcOverlayRules> where it is used for
+selecting the current rule set if MARCOverlayRules is enabled.
+See L</ApplyMarcOverlayRules> for more details.
+
+=item C<disable_autolink>
+
+Unless C<disable_autolink> is passed ModBiblio will relink record headings
 to authorities based on settings in the system preferences. This flag allows
 us to not relink records when the authority linker is saving modifications.
 
 to authorities based on settings in the system preferences. This flag allows
 us to not relink records when the authority linker is saving modifications.
 
+=item C<skip_holds_queue>
+
+Unless C<skip_holds_queue> is passed, ModBiblio will trigger the BatchUpdateBiblioHoldsQueue
+task to rebuild the holds queue for the biblio if I<RealTimeHoldsQueue> is enabled.
+
+=back
+
 Returns 1 on success 0 on failure
 
 =cut
 
 sub ModBiblio {
 Returns 1 on success 0 on failure
 
 =cut
 
 sub ModBiblio {
-    my ( $record, $biblionumber, $frameworkcode, $disable_autolink ) = @_;
+    my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
+
+    $options //= {};
+    my $skip_record_index = $options->{skip_record_index} || 0;
+
     if (!$record) {
         carp 'No record passed to ModBiblio';
         return 0;
     }
 
     if ( C4::Context->preference("CataloguingLog") ) {
     if (!$record) {
         carp 'No record passed to ModBiblio';
         return 0;
     }
 
     if ( C4::Context->preference("CataloguingLog") ) {
-        my $newrecord = GetMarcBiblio({ biblionumber => $biblionumber });
-        logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
+        my $biblio = Koha::Biblios->find($biblionumber);
+        logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $biblio->metadata->record->as_formatted );
     }
 
     }
 
-    if ( !$disable_autolink && C4::Context->preference('BiblioAddsAuthorities') ) {
+    if ( !$options->{disable_autolink} && C4::Context->preference('BiblioAddsAuthorities') ) {
         BiblioAutoLink( $record, $frameworkcode );
     }
 
         BiblioAutoLink( $record, $frameworkcode );
     }
 
@@ -367,6 +389,21 @@ sub ModBiblio {
 
     _strip_item_fields($record, $frameworkcode);
 
 
     _strip_item_fields($record, $frameworkcode);
 
+    # apply overlay rules
+    if (   C4::Context->preference('MARCOverlayRules')
+        && $biblionumber
+        && defined $options
+        && exists $options->{overlay_context} )
+    {
+        $record = ApplyMarcOverlayRules(
+            {
+                biblionumber    => $biblionumber,
+                record          => $record,
+                overlay_context => $options->{overlay_context},
+            }
+        );
+    }
+
     # update biblionumber and biblioitemnumber in MARC
     # FIXME - this is assuming a 1 to 1 relationship between
     # biblios and biblioitems
     # update biblionumber and biblioitemnumber in MARC
     # FIXME - this is assuming a 1 to 1 relationship between
     # biblios and biblioitems
@@ -377,13 +414,13 @@ sub ModBiblio {
     _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
 
     # load the koha-table data object
     _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
 
     # load the koha-table data object
-    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
+    my $oldbiblio = TransformMarcToKoha({ record => $record });
 
     # update MARC subfield that stores biblioitems.cn_sort
     _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
 
     # update the MARC record (that now contains biblio and items) with the new record data
 
     # update MARC subfield that stores biblioitems.cn_sort
     _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
 
     # update the MARC record (that now contains biblio and items) with the new record data
-    &ModBiblioMarc( $record, $biblionumber );
+    ModBiblioMarc( $record, $biblionumber, { skip_record_index => $skip_record_index } );
 
     # modify the other koha tables
     _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
 
     # modify the other koha tables
     _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
@@ -396,6 +433,12 @@ sub ModBiblio {
         C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
     }
 
         C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
     }
 
+    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+        {
+            biblio_ids => [ $biblionumber ]
+        }
+    ) unless $options->{skip_holds_queue} or !C4::Context->preference('RealTimeHoldsQueue');
+
     return 1;
 }
 
     return 1;
 }
 
@@ -424,7 +467,7 @@ sub _strip_item_fields {
 
 =head2 DelBiblio
 
 
 =head2 DelBiblio
 
-  my $error = &DelBiblio($biblionumber);
+  my $error = &DelBiblio($biblionumber, $params);
 
 Exported function (core API) for deleting a biblio in koha.
 Deletes biblio record from Zebra and Koha tables (biblio & biblioitems)
 
 Exported function (core API) for deleting a biblio in koha.
 Deletes biblio record from Zebra and Koha tables (biblio & biblioitems)
@@ -433,6 +476,15 @@ Checks to make sure that the biblio has no items attached.
 return:
 C<$error> : undef unless an error occurs
 
 return:
 C<$error> : undef unless an error occurs
 
+I<$params> is a hashref containing extra parameters. Valid keys are:
+
+=over 4
+
+=item B<skip_holds_queue>: used when the holds queue update will be handled by the caller
+
+=item B<skip_record_index>: used when the indexing schedulling will be handled by the caller
+
+=back
 =cut
 
 sub DelBiblio {
 =cut
 
 sub DelBiblio {
@@ -458,7 +510,8 @@ sub DelBiblio {
     # We delete any existing holds
     my $holds = $biblio->holds;
     while ( my $hold = $holds->next ) {
     # We delete any existing holds
     my $holds = $biblio->holds;
     while ( my $hold = $holds->next ) {
-        $hold->cancel;
+        # no need to update the holds queue on each step, we'll do it at the end
+        $hold->cancel({ skip_holds_queue => 1 });
     }
 
     unless ( $params->{skip_record_index} ){
     }
 
     unless ( $params->{skip_record_index} ){
@@ -487,6 +540,12 @@ sub DelBiblio {
 
     logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
 
 
     logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
 
+    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+        {
+            biblio_ids => [ $biblionumber ]
+        }
+    ) unless $params->{skip_holds_queue} or !C4::Context->preference('RealTimeHoldsQueue');
+
     return;
 }
 
     return;
 }
 
@@ -650,7 +709,7 @@ sub LinkBibHeadingsToAuthorities {
                         $marcrecordauth->insert_fields_ordered(
                             MARC::Field->new(
                                 '667', '', '',
                         $marcrecordauth->insert_fields_ordered(
                             MARC::Field->new(
                                 '667', '', '',
-                                'a' => "Machine generated authority record."
+                                'a' => C4::Context->preference('GenerateAuthorityField667')
                             )
                         );
                         my $cite =
                             )
                         );
                         my $cite =
@@ -660,7 +719,7 @@ sub LinkBibHeadingsToAuthorities {
                         $cite =~ s/^[\s\,]*//;
                         $cite =~ s/[\s\,]*$//;
                         $cite =
                         $cite =~ s/^[\s\,]*//;
                         $cite =~ s/[\s\,]*$//;
                         $cite =
-                            "Work cat.: ("
+                            C4::Context->preference('GenerateAuthorityField670') . ": ("
                           . ( $library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') ) . ")"
                           . $bib->subfield( '999', 'c' ) . ": "
                           . $cite;
                           . ( $library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') ) . ")"
                           . $bib->subfield( '999', 'c' ) . ": "
                           . $cite;
@@ -1185,14 +1244,8 @@ sub GetMarcBiblio {
         return;
     }
 
         return;
     }
 
-    my $dbh          = C4::Context->dbh;
-    my $sth          = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=? ");
-    $sth->execute($biblionumber);
-    my $row     = $sth->fetchrow_hashref;
-    my $biblioitemnumber = $row->{'biblioitemnumber'};
     my $marcxml = GetXmlBiblio( $biblionumber );
     $marcxml = StripNonXmlChars( $marcxml );
     my $marcxml = GetXmlBiblio( $biblionumber );
     $marcxml = StripNonXmlChars( $marcxml );
-    my $frameworkcode = GetFrameworkCode($biblionumber);
     MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
     my $record = MARC::Record->new();
 
     MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
     my $record = MARC::Record->new();
 
@@ -1204,8 +1257,6 @@ sub GetMarcBiblio {
         if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
         return unless $record;
 
         if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
         return unless $record;
 
-        C4::Biblio::_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber,
-            $biblioitemnumber );
         C4::Biblio::EmbedItemsInMarcBiblio({
             marc_record  => $record,
             biblionumber => $biblionumber,
         C4::Biblio::EmbedItemsInMarcBiblio({
             marc_record  => $record,
             biblionumber => $biblionumber,
@@ -1601,7 +1652,7 @@ sub GetMarcSubjects {
                 push @link_loop, {
                     limit    => $subject_limit,
                     'link'   => $linkvalue,
                 push @link_loop, {
                     limit    => $subject_limit,
                     'link'   => $linkvalue,
-                    operator => (scalar @link_loop) ? ' and ' : undef
+                    operator => (scalar @link_loop) ? ' AND ' : undef
                 };
             }
             my @this_link_loop = @link_loop;
                 };
             }
             my @this_link_loop = @link_loop;
@@ -1625,105 +1676,6 @@ sub GetMarcSubjects {
     return \@marcsubjects;
 }    #end getMARCsubjects
 
     return \@marcsubjects;
 }    #end getMARCsubjects
 
-=head2 GetMarcAuthors
-
-  authors = GetMarcAuthors($record,$marcflavour);
-
-Get all authors from the MARC record and returns them in an array.
-The authors are stored in different fields depending on MARC flavour
-
-=cut
-
-sub GetMarcAuthors {
-    my ( $record, $marcflavour ) = @_;
-    if (!$record) {
-        carp 'GetMarcAuthors called on undefined record';
-        return;
-    }
-    my ( $mintag, $maxtag, $fields_filter );
-
-    # tagslib useful only for UNIMARC author responsibilities
-    my $tagslib;
-    if ( $marcflavour eq "UNIMARC" ) {
-        # FIXME : we don't have the framework available, we take the default framework. May be buggy on some setups, will be usually correct.
-        $tagslib = GetMarcStructure( 1, '', { unsafe => 1 });
-        $mintag = "700";
-        $maxtag = "712";
-        $fields_filter = '7..';
-    } else { # marc21
-        $mintag = "700";
-        $maxtag = "720";
-        $fields_filter = '7..';
-    }
-
-    my @marcauthors;
-    my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
-
-    foreach my $field ( $record->field($fields_filter) ) {
-        next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
-        my @subfields_loop;
-        my @link_loop;
-        my @subfields  = $field->subfields();
-        my $count_auth = 0;
-
-        # if there is an authority link, build the link with Koha-Auth-Number: subfield9
-        my $subfield9 = $field->subfield('9');
-        if ($subfield9) {
-            my $linkvalue = $subfield9;
-            $linkvalue =~ s/(\(|\))//g;
-            @link_loop = ( { 'limit' => 'an', 'link' => $linkvalue } );
-        }
-
-        # other subfields
-        my $unimarc3;
-        for my $authors_subfield (@subfields) {
-            next if ( $authors_subfield->[0] eq '9' );
-
-            # unimarc3 contains the $3 of the author for UNIMARC.
-            # For french academic libraries, it's the "ppn", and it's required for idref webservice
-            $unimarc3 = $authors_subfield->[1] if $marcflavour eq 'UNIMARC' and $authors_subfield->[0] =~ /3/;
-
-            # don't load unimarc subfields 3, 5
-            next if ( $marcflavour eq 'UNIMARC' and ( $authors_subfield->[0] =~ /3|5/ ) );
-
-            my $code = $authors_subfield->[0];
-            my $value        = $authors_subfield->[1];
-            my $linkvalue    = $value;
-            $linkvalue =~ s/(\(|\))//g;
-            # UNIMARC author responsibility
-            if ( $marcflavour eq 'UNIMARC' and $code eq '4' ) {
-                $value = GetAuthorisedValueDesc( $field->tag(), $code, $value, '', $tagslib );
-                $linkvalue = "($value)";
-            }
-            # if no authority link, build a search query
-            unless ($subfield9) {
-                push @link_loop, {
-                    limit    => 'au',
-                    'link'   => $linkvalue,
-                    operator => (scalar @link_loop) ? ' and ' : undef
-                };
-            }
-            my @this_link_loop = @link_loop;
-            # do not display $0
-            unless ( $code eq '0') {
-                push @subfields_loop, {
-                    tag       => $field->tag(),
-                    code      => $code,
-                    value     => $value,
-                    link_loop => \@this_link_loop,
-                    separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
-                };
-            }
-        }
-        push @marcauthors, {
-            MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop,
-            authoritylink => $subfield9,
-            unimarc3 => $unimarc3
-        };
-    }
-    return \@marcauthors;
-}
-
 =head2 GetMarcUrls
 
   $marcurls = GetMarcUrls($record,$marcflavour);
 =head2 GetMarcUrls
 
   $marcurls = GetMarcUrls($record,$marcflavour);
@@ -1838,7 +1790,7 @@ sub GetMarcSeries {
 
             push @link_loop, {
                 'link' => $linkvalue,
 
             push @link_loop, {
                 'link' => $linkvalue,
-                operator => (scalar @link_loop) ? ' and ' : undef
+                operator => (scalar @link_loop) ? ' AND ' : undef
             };
 
             if ($volume_number) {
             };
 
             if ($volume_number) {
@@ -1996,8 +1948,9 @@ This function returns a host field populated with data from the host record, the
 sub PrepHostMarcField {
     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
     $marcflavour ||="MARC21";
 sub PrepHostMarcField {
     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
     $marcflavour ||="MARC21";
-    
-    my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
+
+    my $biblio = Koha::Biblios->find($hostbiblionumber);
+    my $hostrecord = $biblio->metadata->record;
     my $item = Koha::Items->find($hostitemnumber);
 
        my $hostmarcfield;
     my $item = Koha::Items->find($hostitemnumber);
 
        my $hostmarcfield;
@@ -2337,7 +2290,7 @@ sub TransformHtmlToMarc {
 
 =head2 TransformMarcToKoha
 
 
 =head2 TransformMarcToKoha
 
-    $result = TransformMarcToKoha( $record, undef, $limit )
+    $result = TransformMarcToKoha({ record => $record, limit_table => $limit })
 
 Extract data from a MARC bib record into a hashref representing
 Koha biblio, biblioitems, and items fields.
 
 Extract data from a MARC bib record into a hashref representing
 Koha biblio, biblioitems, and items fields.
@@ -2348,9 +2301,11 @@ hash_ref.
 =cut
 
 sub TransformMarcToKoha {
 =cut
 
 sub TransformMarcToKoha {
-    my ( $record, $frameworkcode, $limit_table ) = @_;
-    # FIXME  Parameter $frameworkcode is obsolete and will be removed
-    $limit_table //= q{};
+    my ( $params ) = @_;
+
+    my $record = $params->{record};
+    my $limit_table = $params->{limit_table} // q{};
+    my $kohafields = $params->{kohafields};
 
     my $result = {};
     if (!defined $record) {
 
     my $result = {};
     if (!defined $record) {
@@ -2368,13 +2323,34 @@ sub TransformMarcToKoha {
     # The next call acknowledges Default as the authoritative framework
     # for Koha to MARC mappings.
     my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
     # The next call acknowledges Default as the authoritative framework
     # for Koha to MARC mappings.
     my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
-    foreach my $kohafield ( keys %{ $mss } ) {
+    @{$kohafields} = keys %{ $mss } unless $kohafields;
+    foreach my $kohafield ( @{$kohafields} ) {
         my ( $table, $column ) = split /[.]/, $kohafield, 2;
         next unless $tables{$table};
         my ( $table, $column ) = split /[.]/, $kohafield, 2;
         next unless $tables{$table};
-        my $val = TransformMarcToKohaOneField( $kohafield, $record );
-        next if !defined $val;
+        my ( $value, @values );
+        foreach my $fldhash ( @{$mss->{$kohafield}} ) {
+            my $tag = $fldhash->{tagfield};
+            my $sub = $fldhash->{tagsubfield};
+            foreach my $fld ( $record->field($tag) ) {
+                if( $sub eq '@' || $fld->is_control_field ) {
+                    push @values, $fld->data if $fld->data;
+                } else {
+                    push @values, grep { $_ } $fld->subfield($sub);
+                }
+            }
+        }
+        if ( @values ){
+            $value = join ' | ', uniq(@values);
+
+            # Additional polishing for individual kohafields
+            if( $kohafield =~ /copyrightdate|publicationyear/ ) {
+                $value = _adjust_pubyear( $value );
+            }
+        }
+
+        next if !defined $value;
         my $key = _disambiguate( $table, $column );
         my $key = _disambiguate( $table, $column );
-        $result->{$key} = $val;
+        $result->{$key} = $value;
     }
     return $result;
 }
     }
     return $result;
 }
@@ -2418,44 +2394,9 @@ sub _disambiguate {
 
 }
 
 
 }
 
-=head2 TransformMarcToKohaOneField
-
-    $val = TransformMarcToKohaOneField( 'biblio.title', $marc );
-
-    Note: The authoritative Default framework is used implicitly.
-
-=cut
-
-sub TransformMarcToKohaOneField {
-    my ( $kohafield, $marc ) = @_;
-
-    my ( @rv, $retval );
-    my @mss = GetMarcSubfieldStructureFromKohaField($kohafield);
-    foreach my $fldhash ( @mss ) {
-        my $tag = $fldhash->{tagfield};
-        my $sub = $fldhash->{tagsubfield};
-        foreach my $fld ( $marc->field($tag) ) {
-            if( $sub eq '@' || $fld->is_control_field ) {
-                push @rv, $fld->data if $fld->data;
-            } else {
-                push @rv, grep { $_ } $fld->subfield($sub);
-            }
-        }
-    }
-    return unless @rv;
-    $retval = join ' | ', uniq(@rv);
-
-    # Additional polishing for individual kohafields
-    if( $kohafield =~ /copyrightdate|publicationyear/ ) {
-        $retval = _adjust_pubyear( $retval );
-    }
-
-    return $retval;
-}
-
 =head2 _adjust_pubyear
 
 =head2 _adjust_pubyear
 
-    Helper routine for TransformMarcToKohaOneField
+    Helper routine for TransformMarcToKoha
 
 =cut
 
 
 =cut
 
@@ -2466,16 +2407,11 @@ sub _adjust_pubyear {
         $retval = $1;
     } elsif( $retval =~ m/(\d\d\d\d)/ && $1 > 0 ) {
         $retval = $1;
         $retval = $1;
     } elsif( $retval =~ m/(\d\d\d\d)/ && $1 > 0 ) {
         $retval = $1;
-    } elsif( $retval =~ m/
-             (?<year>\d)[-]?[.Xx?]{3}
-            |(?<year>\d{2})[.Xx?]{2}
-            |(?<year>\d{3})[.Xx?]
-            |(?<year>\d)[-]{3}\?
-            |(?<year>\d\d)[-]{2}\?
-            |(?<year>\d{3})[-]\?
-    /xms ) { # the form 198-? occurred in Dutch ISBD rules
-        my $digits = $+{year};
-        $retval = $digits * ( 10 ** ( 4 - length($digits) ));
+    } elsif( $retval =~ m/(?<year>\d{1,3})[.Xx?-]/ ) {
+        # See also bug 24674: enough to look at one unknown year char like .Xx-?
+        # At this point in code 1234? or 1234- already passed the earlier regex
+        # Things like 2-, 1xx, 1??? are now converted to a four positions-year.
+        $retval = $+{year} * ( 10 ** (4-length($+{year})) );
     } else {
         $retval = undef;
     }
     } else {
         $retval = undef;
     }
@@ -2921,7 +2857,7 @@ sub _koha_delete_biblio_metadata {
 
 =head2 ModBiblioMarc
 
 
 =head2 ModBiblioMarc
 
-  &ModBiblioMarc($newrec,$biblionumber);
+  ModBiblioMarc($newrec,$biblionumber);
 
 Add MARC XML data for a biblio to koha
 
 
 Add MARC XML data for a biblio to koha
 
@@ -2932,12 +2868,14 @@ Function exported, but should NOT be used, unless you really know what you're do
 sub ModBiblioMarc {
     # pass the MARC::Record to this function, and it will create the records in
     # the marcxml field
 sub ModBiblioMarc {
     # pass the MARC::Record to this function, and it will create the records in
     # the marcxml field
-    my ( $record, $biblionumber ) = @_;
+    my ( $record, $biblionumber, $params ) = @_;
     if ( !$record ) {
         carp 'ModBiblioMarc passed an undefined record';
         return;
     }
 
     if ( !$record ) {
         carp 'ModBiblioMarc passed an undefined record';
         return;
     }
 
+    my $skip_record_index = $params->{skip_record_index} || 0;
+
     # Clone record as it gets modified
     $record = $record->clone();
     my $dbh    = C4::Context->dbh;
     # Clone record as it gets modified
     $record = $record->clone();
     my $dbh    = C4::Context->dbh;
@@ -2997,8 +2935,10 @@ sub ModBiblioMarc {
     $m_rs->metadata( $record->as_xml_record($encoding) );
     $m_rs->store;
 
     $m_rs->metadata( $record->as_xml_record($encoding) );
     $m_rs->store;
 
-    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
-    $indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" );
+    unless ( $skip_record_index ) {
+        my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+        $indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" );
+    }
 
     return $biblionumber;
 }
 
     return $biblionumber;
 }
@@ -3013,7 +2953,9 @@ Generate the host item entry for an analytic child entry
 sub prepare_host_field {
     my ( $hostbiblio, $marcflavour ) = @_;
     $marcflavour ||= C4::Context->preference('marcflavour');
 sub prepare_host_field {
     my ( $hostbiblio, $marcflavour ) = @_;
     $marcflavour ||= C4::Context->preference('marcflavour');
-    my $host = GetMarcBiblio({ biblionumber => $hostbiblio });
+
+    my $biblio = Koha::Biblios->find($hostbiblio);
+    my $host = $biblio->metadata->record;
     # unfortunately as_string does not 'do the right thing'
     # if field returns undef
     my %sfd;
     # unfortunately as_string does not 'do the right thing'
     # if field returns undef
     my %sfd;
@@ -3148,19 +3090,20 @@ Update the total issue count for a particular bib record.
 =cut
 
 sub UpdateTotalIssues {
 =cut
 
 sub UpdateTotalIssues {
-    my ($biblionumber, $increase, $value) = @_;
+    my ($biblionumber, $increase, $value, $skip_holds_queue) = @_;
     my $totalissues;
 
     my $totalissues;
 
-    my $record = GetMarcBiblio({ biblionumber => $biblionumber });
-    unless ($record) {
-        carp "UpdateTotalIssues could not get biblio record";
-        return;
-    }
-    my $biblio = Koha::Biblios->find( $biblionumber );
+    my $biblio = Koha::Biblios->find($biblionumber);
     unless ($biblio) {
         carp "UpdateTotalIssues could not get datas of biblio";
         return;
     }
     unless ($biblio) {
         carp "UpdateTotalIssues could not get datas of biblio";
         return;
     }
+
+    my $record = $biblio->metadata->record;
+    unless ($record) {
+        carp "UpdateTotalIssues could not get biblio record";
+        return;
+    }
     my $biblioitem = $biblio->biblioitem;
     my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField( 'biblioitems.totalissues' );
     unless ($totalissuestag) {
     my $biblioitem = $biblio->biblioitem;
     my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField( 'biblioitems.totalissues' );
     unless ($totalissuestag) {
@@ -3182,7 +3125,7 @@ sub UpdateTotalIssues {
          $record->insert_grouped_field($field);
      }
 
          $record->insert_grouped_field($field);
      }
 
-     return ModBiblio($record, $biblionumber, $biblio->frameworkcode);
+     return ModBiblio($record, $biblionumber, $biblio->frameworkcode, { skip_holds_queue => $skip_holds_queue });
 }
 
 =head2 RemoveAllNsb
 }
 
 =head2 RemoveAllNsb
@@ -3233,8 +3176,66 @@ sub RemoveAllNsb {
     return $record;
 }
 
     return $record;
 }
 
-1;
+=head2 ApplyMarcOverlayRules
+
+    my $record = ApplyMarcOverlayRules($params)
 
 
+Applies marc merge rules to a record.
+
+C<$params> is expected to be a hashref with below keys defined.
+
+=over 4
+
+=item C<biblionumber>
+biblionumber of old record
+
+=item C<record>
+Incoming record that will be merged with old record
+
+=item C<overlay_context>
+hashref containing at least one context module and filter value on
+the form {module => filter, ...}.
+
+=back
+
+Returns:
+
+=over 4
+
+=item C<$record>
+
+Merged MARC record based with merge rules for C<context> applied. If no old
+record for C<biblionumber> can be found, C<record> is returned unchanged.
+Default action when no matching context is found to return C<record> unchanged.
+If no rules are found for a certain field tag the default is to overwrite with
+fields with this field tag from C<record>.
+
+=back
+
+=cut
+
+sub ApplyMarcOverlayRules {
+    my ($params) = @_;
+    my $biblionumber = $params->{biblionumber};
+    my $incoming_record = $params->{record};
+
+    if (!$biblionumber) {
+        carp 'ApplyMarcOverlayRules called on undefined biblionumber';
+        return;
+    }
+    if (!$incoming_record) {
+        carp 'ApplyMarcOverlayRules called on undefined record';
+        return;
+    }
+    my $biblio = Koha::Biblios->find($biblionumber);
+    my $old_record = $biblio->metadata->record;
+
+    # Skip overlay rules if called with no context
+    if ($old_record && defined $params->{overlay_context}) {
+        return Koha::MarcOverlayRules->merge_records($old_record, $incoming_record, $params->{overlay_context});
+    }
+    return $incoming_record;
+}
 
 =head2 _after_biblio_action_hooks
 
 
 =head2 _after_biblio_action_hooks
 
@@ -3259,6 +3260,8 @@ sub _after_biblio_action_hooks {
     );
 }
 
     );
 }
 
+1;
+
 __END__
 
 =head1 AUTHOR
 __END__
 
 =head1 AUTHOR