Bug 20912: (QA follow-up) Rebase error corrections
[koha.git] / C4 / Biblio.pm
index 51666e7..c12b108 100644 (file)
@@ -20,9 +20,69 @@ package C4::Biblio;
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
+
+use vars qw(@ISA @EXPORT);
+BEGIN {
+    require Exporter;
+    @ISA = qw(Exporter);
+
+    @EXPORT = qw(
+        AddBiblio
+        GetBiblioData
+        GetMarcBiblio
+        GetRecordValue
+        GetISBDView
+        GetMarcControlnumber
+        GetMarcNotes
+        GetMarcISBN
+        GetMarcISSN
+        GetMarcSubjects
+        GetMarcAuthors
+        GetMarcSeries
+        GetMarcHosts
+        GetMarcUrls
+        GetUsedMarcStructure
+        GetXmlBiblio
+        GetCOinSBiblio
+        GetMarcPrice
+        MungeMarcPrice
+        GetMarcQuantity
+        GetAuthorisedValueDesc
+        GetMarcStructure
+        IsMarcStructureInternal
+        GetMarcFromKohaField
+        GetMarcSubfieldStructureFromKohaField
+        GetFrameworkCode
+        TransformKohaToMarc
+        PrepHostMarcField
+        CountItemsIssued
+        CountBiblioInOrders
+        ModBiblio
+        ModZebra
+        UpdateTotalIssues
+        RemoveAllNsb
+        DelBiblio
+        BiblioAutoLink
+        LinkBibHeadingsToAuthorities
+        TransformMarcToKoha
+        TransformHtmlToMarc
+        TransformHtmlToXml
+        prepare_host_field
+    );
+
+    # Internal functions
+    # those functions are exported but should not be used
+    # they are useful in a few circumstances, so they are exported,
+    # but don't use them unless you are a core developer ;-)
+    push @EXPORT, qw(
+      ModBiblioMarc
+    );
+}
+
 use Carp;
 
 use Encode qw( decode is_utf8 );
 use Carp;
 
 use Encode qw( decode is_utf8 );
+use List::MoreUtils qw( uniq );
 use MARC::Record;
 use MARC::File::USMARC;
 use MARC::File::XML;
 use MARC::Record;
 use MARC::File::USMARC;
 use MARC::File::XML;
@@ -41,104 +101,15 @@ use C4::Debug;
 use Koha::Caches;
 use Koha::Authority::Types;
 use Koha::Acquisition::Currencies;
 use Koha::Caches;
 use Koha::Authority::Types;
 use Koha::Acquisition::Currencies;
-use Koha::Biblio::Metadata;
 use Koha::Biblio::Metadatas;
 use Koha::Holds;
 use Koha::ItemTypes;
 use Koha::SearchEngine;
 use Koha::Libraries;
 use Koha::Biblio::Metadatas;
 use Koha::Holds;
 use Koha::ItemTypes;
 use Koha::SearchEngine;
 use Koha::Libraries;
+use Koha::Util::MARC;
 
 
-use vars qw(@ISA @EXPORT);
 use vars qw($debug $cgi_debug);
 
 use vars qw($debug $cgi_debug);
 
-BEGIN {
-
-    require Exporter;
-    @ISA = qw( Exporter );
-
-    # to add biblios
-    # EXPORTED FUNCTIONS.
-    push @EXPORT, qw(
-      &AddBiblio
-    );
-
-    # to get something
-    push @EXPORT, qw(
-      GetBiblioData
-      GetMarcBiblio
-      GetBiblioItemData
-      GetBiblioItemInfosOf
-      GetBiblioItemByBiblioNumber
-
-      &GetRecordValue
-
-      &GetISBDView
-
-      &GetMarcControlnumber
-      &GetMarcNotes
-      &GetMarcISBN
-      &GetMarcISSN
-      &GetMarcSubjects
-      &GetMarcAuthors
-      &GetMarcSeries
-      &GetMarcHosts
-      GetMarcUrls
-      &GetUsedMarcStructure
-      &GetXmlBiblio
-      &GetCOinSBiblio
-      &GetMarcPrice
-      &MungeMarcPrice
-      &GetMarcQuantity
-
-      &GetAuthorisedValueDesc
-      &GetMarcStructure
-      &IsMarcStructureInternal
-      &GetMarcFromKohaField
-      &GetMarcSubfieldStructureFromKohaField
-      &GetFrameworkCode
-      &TransformKohaToMarc
-      &PrepHostMarcField
-
-      &CountItemsIssued
-      &CountBiblioInOrders
-    );
-
-    # To modify something
-    push @EXPORT, qw(
-      &ModBiblio
-      &ModZebra
-      &UpdateTotalIssues
-      &RemoveAllNsb
-    );
-
-    # To delete something
-    push @EXPORT, qw(
-      &DelBiblio
-    );
-
-    # To link headings in a bib record
-    # to authority records.
-    push @EXPORT, qw(
-      &BiblioAutoLink
-      &LinkBibHeadingsToAuthorities
-    );
-
-    # Internal functions
-    # those functions are exported but should not be used
-    # they are useful in a few circumstances, so they are exported,
-    # but don't use them unless you are a core developer ;-)
-    push @EXPORT, qw(
-      &ModBiblioMarc
-    );
-
-    # Others functions
-    push @EXPORT, qw(
-      &TransformMarcToKoha
-      &TransformHtmlToMarc
-      &TransformHtmlToXml
-      prepare_host_field
-    );
-}
 
 =head1 NAME
 
 
 =head1 NAME
 
@@ -236,6 +207,10 @@ sub AddBiblio {
         $defer_marc_save = 1;
     }
 
         $defer_marc_save = 1;
     }
 
+    if (C4::Context->preference('BiblioAddsAuthorities')) {
+        BiblioAutoLink( $record, $frameworkcode );
+    }
+
     my ( $biblionumber, $biblioitemnumber, $error );
     my $dbh = C4::Context->dbh;
 
     my ( $biblionumber, $biblioitemnumber, $error );
     my $dbh = C4::Context->dbh;
 
@@ -297,6 +272,10 @@ sub ModBiblio {
         logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
     }
 
         logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
     }
 
+    if (C4::Context->preference('BiblioAddsAuthorities')) {
+        BiblioAutoLink( $record, $frameworkcode );
+    }
+
     # Cleaning up invalid fields must be done early or SetUTF8Flag is liable to
     # throw an exception which probably won't be handled.
     foreach my $field ($record->fields()) {
     # Cleaning up invalid fields must be done early or SetUTF8Flag is liable to
     # throw an exception which probably won't be handled.
     foreach my $field ($record->fields()) {
@@ -382,6 +361,10 @@ C<$error> : undef unless an error occurs
 
 sub DelBiblio {
     my ($biblionumber) = @_;
 
 sub DelBiblio {
     my ($biblionumber) = @_;
+
+    my $biblio = Koha::Biblios->find( $biblionumber );
+    return unless $biblio; # Should we throw an exception instead?
+
     my $dbh = C4::Context->dbh;
     my $error;    # for error handling
 
     my $dbh = C4::Context->dbh;
     my $error;    # for error handling
 
@@ -404,7 +387,6 @@ sub DelBiblio {
     }
 
     # We delete any existing holds
     }
 
     # We delete any existing holds
-    my $biblio = Koha::Biblios->find( $biblionumber );
     my $holds = $biblio->holds;
     while ( my $hold = $holds->next ) {
         $hold->cancel;
     my $holds = $biblio->holds;
     while ( my $hold = $holds->next ) {
         $hold->cancel;
@@ -556,7 +538,10 @@ sub LinkBibHeadingsToAuthorities {
                         '', '', "a" => "" . $field->subfield('a') );
                     map {
                         $authfield->add_subfields( $_->[0] => $_->[1] )
                         '', '', "a" => "" . $field->subfield('a') );
                     map {
                         $authfield->add_subfields( $_->[0] => $_->[1] )
-                          if ( $_->[0] =~ /[A-z]/ && $_->[0] ne "a" )
+                          if ( $_->[0] =~ /[A-z]/ && $_->[0] ne "a"
+                            && C4::Heading::valid_bib_heading_subfield(
+                                $authority_type->auth_tag_to_report, $_->[0] )
+                            );
                     } $field->subfields();
                     $marcrecordauth->insert_fields_ordered($authfield);
 
                     } $field->subfields();
                     $marcrecordauth->insert_fields_ordered($authfield);
 
@@ -721,58 +706,6 @@ sub GetBiblioData {
     return ($data);
 }    # sub GetBiblioData
 
     return ($data);
 }    # sub GetBiblioData
 
-=head2 &GetBiblioItemData
-
-  $itemdata = &GetBiblioItemData($biblioitemnumber);
-
-Looks up the biblioitem with the given biblioitemnumber. Returns a
-reference-to-hash. The keys are the fields from the C<biblio>,
-C<biblioitems>, and C<itemtypes> tables in the Koha database, except
-that C<biblioitems.notes> is given as C<$itemdata-E<gt>{bnotes}>.
-
-=cut
-
-#'
-sub GetBiblioItemData {
-    my ($biblioitemnumber) = @_;
-    my $dbh                = C4::Context->dbh;
-    my $query              = "SELECT *,biblioitems.notes AS bnotes
-        FROM biblio LEFT JOIN biblioitems on biblio.biblionumber=biblioitems.biblionumber ";
-    unless ( C4::Context->preference('item-level_itypes') ) {
-        $query .= "LEFT JOIN itemtypes on biblioitems.itemtype=itemtypes.itemtype ";
-    }
-    $query .= " WHERE biblioitemnumber = ? ";
-    my $sth = $dbh->prepare($query);
-    my $data;
-    $sth->execute($biblioitemnumber);
-    $data = $sth->fetchrow_hashref;
-    $sth->finish;
-    return ($data);
-}    # sub &GetBiblioItemData
-
-=head2 GetBiblioItemByBiblioNumber
-
-NOTE : This function has been copy/paste from C4/Biblio.pm from head before zebra integration.
-
-=cut
-
-sub GetBiblioItemByBiblioNumber {
-    my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare("Select * FROM biblioitems WHERE biblionumber = ?");
-    my $count          = 0;
-    my @results;
-
-    $sth->execute($biblionumber);
-
-    while ( my $data = $sth->fetchrow_hashref ) {
-        push @results, $data;
-    }
-
-    $sth->finish;
-    return @results;
-}
-
 =head2 GetISBDView 
 
   $isbd = &GetISBDView({
 =head2 GetISBDView 
 
   $isbd = &GetISBDView({
@@ -797,7 +730,7 @@ sub GetISBDView {
     my $framework = $params->{framework};
     my $itemtype  = $framework;
     my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch", $itemtype );
     my $framework = $params->{framework};
     my $itemtype  = $framework;
     my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch", $itemtype );
-    my $tagslib = &GetMarcStructure( 1, $itemtype, { unsafe => 1 } );
+    my $tagslib = GetMarcStructure( 1, $itemtype, { unsafe => 1 } );
 
     my $ISBD = C4::Context->preference($sysprefname);
     my $bloc = $ISBD;
 
     my $ISBD = C4::Context->preference($sysprefname);
     my $bloc = $ISBD;
@@ -909,28 +842,6 @@ sub GetISBDView {
     return $res;
 }
 
     return $res;
 }
 
-=head2 GetBiblioItemInfosOf
-
-  GetBiblioItemInfosOf(@biblioitemnumbers);
-
-=cut
-
-sub GetBiblioItemInfosOf {
-    my @biblioitemnumbers = @_;
-
-    my $biblioitemnumber_values = @biblioitemnumbers ? join( ',', @biblioitemnumbers ) : "''";
-
-    my $dbh = C4::Context->dbh;
-    my $query = "
-        SELECT biblioitemnumber,
-            publicationyear,
-            itemtype
-        FROM biblioitems
-        WHERE biblioitemnumber IN ($biblioitemnumber_values)
-    ";
-    return $dbh->selectall_hashref($query, 'biblioitemnumber');
-}
-
 =head1 FUNCTIONS FOR HANDLING MARC MANAGEMENT
 
 =head2 IsMarcStructureInternal
 =head1 FUNCTIONS FOR HANDLING MARC MANAGEMENT
 
 =head2 IsMarcStructureInternal
@@ -982,19 +893,21 @@ sub GetMarcStructure {
 
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare(
 
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare(
-        "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable 
+        "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable,ind1_defaultvalue,ind2_defaultvalue
         FROM marc_tag_structure 
         WHERE frameworkcode=? 
         ORDER BY tagfield"
     );
     $sth->execute($frameworkcode);
         FROM marc_tag_structure 
         WHERE frameworkcode=? 
         ORDER BY tagfield"
     );
     $sth->execute($frameworkcode);
-    my ( $liblibrarian, $libopac, $tag, $res, $tab, $mandatory, $repeatable );
+    my ( $liblibrarian, $libopac, $tag, $res, $tab, $mandatory, $repeatable, $ind1_defaultvalue, $ind2_defaultvalue );
 
 
-    while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable ) = $sth->fetchrow ) {
+    while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable, $ind1_defaultvalue, $ind2_defaultvalue ) = $sth->fetchrow ) {
         $res->{$tag}->{lib}        = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
         $res->{$tag}->{tab}        = "";
         $res->{$tag}->{mandatory}  = $mandatory;
         $res->{$tag}->{repeatable} = $repeatable;
         $res->{$tag}->{lib}        = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
         $res->{$tag}->{tab}        = "";
         $res->{$tag}->{mandatory}  = $mandatory;
         $res->{$tag}->{repeatable} = $repeatable;
+    $res->{$tag}->{ind1_defaultvalue} = $ind1_defaultvalue;
+    $res->{$tag}->{ind2_defaultvalue} = $ind2_defaultvalue;
     }
 
     $sth = $dbh->prepare(
     }
 
     $sth = $dbh->prepare(
@@ -1074,18 +987,54 @@ sub GetUsedMarcStructure {
     return $sth->fetchall_arrayref( {} );
 }
 
     return $sth->fetchall_arrayref( {} );
 }
 
+=pod
+
 =head2 GetMarcSubfieldStructure
 
 =head2 GetMarcSubfieldStructure
 
+  my $structure = GetMarcSubfieldStructure($frameworkcode, [$params]);
+
+Returns a reference to hash representing MARC subfield structure
+for framework with framework code C<$frameworkcode>, C<$params> is
+optional and may contain additional options.
+
+=over 4
+
+=item C<$frameworkcode>
+
+The framework code.
+
+=item C<$params>
+
+An optional hash reference with additional options.
+The following options are supported:
+
+=over 4
+
+=item unsafe
+
+Pass { unsafe => 1 } do disable cached object cloning,
+and instead get a shared reference, resulting in better
+performance (but care must be taken so that retured object
+is never modified).
+
+Note: If you call GetMarcSubfieldStructure with unsafe => 1, do not modify or
+even autovivify its contents. It is a cached/shared data structure. Your
+changes would be passed around in subsequent calls.
+
+=back
+
+=back
+
 =cut
 
 sub GetMarcSubfieldStructure {
 =cut
 
 sub GetMarcSubfieldStructure {
-    my ( $frameworkcode ) = @_;
+    my ( $frameworkcode, $params ) = @_;
 
     $frameworkcode //= '';
 
     my $cache     = Koha::Caches->get_instance();
     my $cache_key = "MarcSubfieldStructure-$frameworkcode";
 
     $frameworkcode //= '';
 
     my $cache     = Koha::Caches->get_instance();
     my $cache_key = "MarcSubfieldStructure-$frameworkcode";
-    my $cached    = $cache->get_from_cache($cache_key);
+    my $cached  = $cache->get_from_cache($cache_key, { unsafe => ($params && $params->{unsafe}) });
     return $cached if $cached;
 
     my $dbh = C4::Context->dbh;
     return $cached if $cached;
 
     my $dbh = C4::Context->dbh;
@@ -1109,43 +1058,54 @@ sub GetMarcSubfieldStructure {
 
 =head2 GetMarcFromKohaField
 
 
 =head2 GetMarcFromKohaField
 
-  ($MARCfield,$MARCsubfield)=GetMarcFromKohaField($kohafield,$frameworkcode);
+    ( $field,$subfield ) = GetMarcFromKohaField( $kohafield );
+    @fields = GetMarcFromKohaField( $kohafield );
+    $field = GetMarcFromKohaField( $kohafield );
 
 
-Returns the MARC fields & subfields mapped to the koha field 
-for the given frameworkcode or default framework if $frameworkcode is missing
+    Returns the MARC fields & subfields mapped to $kohafield.
+    Since the Default framework is considered as authoritative for such
+    mappings, the former frameworkcode parameter is obsoleted.
+
+    In list context all mappings are returned; there can be multiple
+    mappings. Note that in the above example you could miss a second
+    mappings in the first call.
+    In scalar context only the field tag of the first mapping is returned.
 
 =cut
 
 sub GetMarcFromKohaField {
 
 =cut
 
 sub GetMarcFromKohaField {
-    my ( $kohafield, $frameworkcode ) = @_;
-    return (0, undef) unless $kohafield;
-    my $mss = GetMarcSubfieldStructure( $frameworkcode );
+    my ( $kohafield ) = @_;
+    return unless $kohafield;
+    # The next call uses the Default framework since it is AUTHORITATIVE
+    # for all Koha to MARC mappings.
+    my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
     my @retval;
     foreach( @{ $mss->{$kohafield} } ) {
         push @retval, $_->{tagfield}, $_->{tagsubfield};
     }
     my @retval;
     foreach( @{ $mss->{$kohafield} } ) {
         push @retval, $_->{tagfield}, $_->{tagsubfield};
     }
-    return wantarray ? @retval : $retval[0];
+    return wantarray ? @retval : ( @retval ? $retval[0] : undef );
 }
 
 =head2 GetMarcSubfieldStructureFromKohaField
 
 }
 
 =head2 GetMarcSubfieldStructureFromKohaField
 
-    my $subfield_structure = &GetMarcSubfieldStructureFromKohaField($kohafield, $frameworkcode);
-
-Returns a hashref where keys are marc_subfield_structure column names for the
-row where kohafield=$kohafield for the given framework code.
-In list context returns a list of those hashrefs in case duplicate Koha to
-MARC mappings exist.
+    my $str = GetMarcSubfieldStructureFromKohaField( $kohafield );
 
 
-$frameworkcode is optional. If not given, then the default framework is used.
+    Returns marc subfield structure information for $kohafield.
+    The Default framework is used, since it is authoritative for kohafield
+    mappings.
+    In list context returns a list of all hashrefs, since there may be
+    multiple mappings. In scalar context the first hashref is returned.
 
 =cut
 
 sub GetMarcSubfieldStructureFromKohaField {
 
 =cut
 
 sub GetMarcSubfieldStructureFromKohaField {
-    my ( $kohafield, $frameworkcode ) = @_;
+    my ( $kohafield ) = @_;
 
     return unless $kohafield;
 
 
     return unless $kohafield;
 
-    my $mss = GetMarcSubfieldStructure( $frameworkcode );
+    # The next call uses the Default framework since it is AUTHORITATIVE
+    # for all Koha to MARC mappings.
+    my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
     return unless $mss->{$kohafield};
     return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
 }
     return unless $mss->{$kohafield};
     return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
 }
@@ -1155,7 +1115,8 @@ sub GetMarcSubfieldStructureFromKohaField {
   my $record = GetMarcBiblio({
       biblionumber => $biblionumber,
       embed_items  => $embeditems,
   my $record = GetMarcBiblio({
       biblionumber => $biblionumber,
       embed_items  => $embeditems,
-      opac         => $opac });
+      opac         => $opac,
+      borcat       => $patron_category });
 
 Returns MARC::Record representing a biblio record, or C<undef> if the
 biblionumber doesn't exist.
 
 Returns MARC::Record representing a biblio record, or C<undef> if the
 biblionumber doesn't exist.
@@ -1179,6 +1140,12 @@ set to true to include item information.
 set to true to make the result suited for OPAC view. This causes things like
 OpacHiddenItems to be applied.
 
 set to true to make the result suited for OPAC view. This causes things like
 OpacHiddenItems to be applied.
 
+=item C<$borcat>
+
+If the OpacHiddenItemsExceptions system preference is set, this patron category
+can be used to make visible OPAC items which would be normally hidden.
+It only makes sense in combination both embed_items and opac values true.
+
 =back
 
 =cut
 =back
 
 =cut
@@ -1194,6 +1161,7 @@ sub GetMarcBiblio {
     my $biblionumber = $params->{biblionumber};
     my $embeditems   = $params->{embed_items} || 0;
     my $opac         = $params->{opac} || 0;
     my $biblionumber = $params->{biblionumber};
     my $embeditems   = $params->{embed_items} || 0;
     my $opac         = $params->{opac} || 0;
+    my $borcat       = $params->{borcat} // q{};
 
     if (not defined $biblionumber) {
         carp 'GetMarcBiblio called with undefined biblionumber';
 
     if (not defined $biblionumber) {
         carp 'GetMarcBiblio called with undefined biblionumber';
@@ -1221,7 +1189,11 @@ sub GetMarcBiblio {
 
         C4::Biblio::_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber,
             $biblioitemnumber );
 
         C4::Biblio::_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber,
             $biblioitemnumber );
-        C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, $opac )
+        C4::Biblio::EmbedItemsInMarcBiblio({
+            marc_record  => $record,
+            biblionumber => $biblionumber,
+            opac         => $opac,
+            borcat       => $borcat })
           if ($embeditems);
 
         return $record;
           if ($embeditems);
 
         return $record;
@@ -1250,7 +1222,7 @@ sub GetXmlBiblio {
         FROM biblio_metadata
         WHERE biblionumber=?
             AND format='marcxml'
         FROM biblio_metadata
         WHERE biblionumber=?
             AND format='marcxml'
-            AND marcflavour=?
+            AND `schema`=?
     |, undef, $biblionumber, C4::Context->preference('marcflavour')
     );
     return $marcxml;
     |, undef, $biblionumber, C4::Context->preference('marcflavour')
     );
     return $marcxml;
@@ -1579,7 +1551,8 @@ sub GetAuthorisedValueDesc {
 
         #---- branch
         if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
 
         #---- branch
         if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
-            return Koha::Libraries->find($value)->branchname;
+            my $branch = Koha::Libraries->find($value);
+            return $branch? $branch->branchname: q{};
         }
 
         #---- itemtypes
         }
 
         #---- itemtypes
@@ -1699,7 +1672,7 @@ sub GetMarcISSN {
 
     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.
-    MARC21 field 555 gets special attention for the $u subfields.
+    MARC21 5XX $u subfields receive special attention as they are URIs.
 
 =cut
 
 
 =cut
 
@@ -1717,12 +1690,16 @@ sub GetMarcNotes {
     foreach my $field ( $record->field($scope) ) {
         my $tag = $field->tag();
         next if $blacklist{ $tag };
     foreach my $field ( $record->field($scope) ) {
         my $tag = $field->tag();
         next if $blacklist{ $tag };
-        if( $marcflavour ne 'UNIMARC' && $tag =~ /555/ ) {
-            # Field 555$u contains URLs
-            # We first push the regular subfields and all $u's separately
-            # Leave further actions to the template
-            push @marcnotes, { marcnote => $field->as_string('abcd') };
+        if( $marcflavour ne 'UNIMARC' && $field->subfield('u') ) {
+            # Field 5XX$u always contains URI
+            # Examples: 505u, 506u, 510u, 514u, 520u, 530u, 538u, 540u, 542u, 552u, 555u, 561u, 563u, 583u
+            # We first push the other subfields, then all $u's separately
+            # Leave further actions to the template (see e.g. opac-detail)
+            my $othersub =
+                join '', ( 'a' .. 't', 'v' .. 'z', '0' .. '9' ); # excl 'u'
+            push @marcnotes, { marcnote => $field->as_string($othersub) };
             foreach my $sub ( $field->subfield('u') ) {
             foreach my $sub ( $field->subfield('u') ) {
+                $sub =~ s/^\s+|\s+$//g; # trim
                 push @marcnotes, { marcnote => $sub };
             }
         } else {
                 push @marcnotes, { marcnote => $sub };
             }
         } else {
@@ -2158,24 +2135,26 @@ sub GetFrameworkCode {
 
 =head2 TransformKohaToMarc
 
 
 =head2 TransformKohaToMarc
 
-    $record = TransformKohaToMarc( $hash )
+    $record = TransformKohaToMarc( $hash [, $params ]  )
 
 
-This function builds partial MARC::Record from a hash
-Hash entries can be from biblio or biblioitems.
+This function builds a (partial) MARC::Record from a hash.
+Hash entries can be from biblio, biblioitems or items.
+The params hash includes the parameter no_split used in C4::Items.
 
 This function is called in acquisition module, to create a basic catalogue
 
 This function is called in acquisition module, to create a basic catalogue
-entry from user entry
+entry from user entry.
 
 =cut
 
 
 sub TransformKohaToMarc {
 
 =cut
 
 
 sub TransformKohaToMarc {
-    my ( $hash, $frameworkcode, $params ) = @_;
+    my ( $hash, $params ) = @_;
     my $record = MARC::Record->new();
     SetMarcUnicodeFlag( $record, C4::Context->preference("marcflavour") );
     my $record = MARC::Record->new();
     SetMarcUnicodeFlag( $record, C4::Context->preference("marcflavour") );
-    # NOTE: Many older calls do not include a frameworkcode. In that case
-    # we default to Default framework.
-    my $mss = GetMarcSubfieldStructure( $frameworkcode//'' );
+
+    # In the next call we use the Default framework, since it is considered
+    # authoritative for Koha to Marc mappings.
+    my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # do not change framewok
     my $tag_hr = {};
     while ( my ($kohafield, $value) = each %$hash ) {
         foreach my $fld ( @{ $mss->{$kohafield} } ) {
     my $tag_hr = {};
     while ( my ($kohafield, $value) = each %$hash ) {
         foreach my $fld ( @{ $mss->{$kohafield} } ) {
@@ -2217,10 +2196,9 @@ sub PrepHostMarcField {
     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
     $marcflavour ||="MARC21";
     
     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
     $marcflavour ||="MARC21";
     
-    require C4::Items;
     my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
     my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
-       my $item = C4::Items::GetItem($hostitemnumber);
-       
+    my $item = Koha::Items->find($hostitemnumber);
+
        my $hostmarcfield;
     if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
        
        my $hostmarcfield;
     if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
        
@@ -2243,7 +2221,7 @@ sub PrepHostMarcField {
 
        #other fields
         my $ed = $hostrecord->subfield('250','a');
 
        #other fields
         my $ed = $hostrecord->subfield('250','a');
-        my $barcode = $item->{'barcode'};
+        my $barcode = $item->barcode;
         my $title = $hostrecord->subfield('245','a');
 
         # record control number, 001 with 003 and prefix
         my $title = $hostrecord->subfield('245','a');
 
         # record control number, 001 with 003 and prefix
@@ -2322,6 +2300,7 @@ sub TransformHtmlToXml {
     my $prevtag = -1;
     my $first   = 1;
     my $j       = -1;
     my $prevtag = -1;
     my $first   = 1;
     my $j       = -1;
+    my $close_last_tag;
     for ( my $i = 0 ; $i < @$tags ; $i++ ) {
 
         if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and @$tags[$i] eq "100" and @$subfields[$i] eq "a" ) {
     for ( my $i = 0 ; $i < @$tags ; $i++ ) {
 
         if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and @$tags[$i] eq "100" and @$subfields[$i] eq "a" ) {
@@ -2342,6 +2321,7 @@ sub TransformHtmlToXml {
         @$values[$i] =~ s/'/&apos;/g;
 
         if ( ( @$tags[$i] ne $prevtag ) ) {
         @$values[$i] =~ s/'/&apos;/g;
 
         if ( ( @$tags[$i] ne $prevtag ) ) {
+            $close_last_tag = 0;
             $j++ unless ( @$tags[$i] eq "" );
             my $indicator1 = eval { substr( @$indicator[$j], 0, 1 ) };
             my $indicator2 = eval { substr( @$indicator[$j], 1, 1 ) };
             $j++ unless ( @$tags[$i] eq "" );
             my $indicator1 = eval { substr( @$indicator[$j], 0, 1 ) };
             my $indicator2 = eval { substr( @$indicator[$j], 1, 1 ) };
@@ -2360,6 +2340,7 @@ sub TransformHtmlToXml {
                     $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
                     $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
                     $first = 0;
                     $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
                     $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
                     $first = 0;
+                    $close_last_tag = 1;
                 } else {
                     $first = 1;
                 }
                 } else {
                     $first = 1;
                 }
@@ -2379,6 +2360,7 @@ sub TransformHtmlToXml {
                         $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
                         $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
                         $first = 0;
                         $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
                         $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
                         $first = 0;
+                        $close_last_tag = 1;
                     }
                 }
             }
                     }
                 }
             }
@@ -2398,13 +2380,14 @@ sub TransformHtmlToXml {
                 if ($first) {
                     $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
                     $first = 0;
                 if ($first) {
                     $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
                     $first = 0;
+                    $close_last_tag = 1;
                 }
                 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
             }
         }
         $prevtag = @$tags[$i];
     }
                 }
                 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
             }
         }
         $prevtag = @$tags[$i];
     }
-    $xml .= "</datafield>\n" if $xml =~ m/<datafield/;
+    $xml .= "</datafield>\n" if $close_last_tag;
     if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and !$unimarc_and_100_exist ) {
 
         #     warn "SETTING 100 for $auth_type";
     if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and !$unimarc_and_100_exist ) {
 
         #     warn "SETTING 100 for $auth_type";
@@ -2560,123 +2543,44 @@ sub TransformHtmlToMarc {
 
 =head2 TransformMarcToKoha
 
 
 =head2 TransformMarcToKoha
 
-  $result = TransformMarcToKoha( $record, $frameworkcode )
+    $result = TransformMarcToKoha( $record, undef, $limit )
 
 Extract data from a MARC bib record into a hashref representing
 
 Extract data from a MARC bib record into a hashref representing
-Koha biblio, biblioitems, and items fields. 
+Koha biblio, biblioitems, and items fields.
 
 If passed an undefined record will log the error and return an empty
 
 If passed an undefined record will log the error and return an empty
-hash_ref
+hash_ref.
 
 =cut
 
 sub TransformMarcToKoha {
     my ( $record, $frameworkcode, $limit_table ) = @_;
 
 =cut
 
 sub TransformMarcToKoha {
     my ( $record, $frameworkcode, $limit_table ) = @_;
+    # FIXME  Parameter $frameworkcode is obsolete and will be removed
+    $limit_table //= q{};
 
     my $result = {};
     if (!defined $record) {
         carp('TransformMarcToKoha called with undefined record');
         return $result;
     }
 
     my $result = {};
     if (!defined $record) {
         carp('TransformMarcToKoha called with undefined record');
         return $result;
     }
-    $limit_table = $limit_table || 0;
-    $frameworkcode //= '';
-
-    my $inverted_field_map = _get_inverted_marc_field_map($frameworkcode);
 
 
-    my %tables = ();
-    if ( defined $limit_table && $limit_table eq 'items' ) {
-        $tables{'items'} = 1;
-    } else {
-        $tables{'items'}       = 1;
-        $tables{'biblio'}      = 1;
-        $tables{'biblioitems'} = 1;
+    my %tables = ( biblio => 1, biblioitems => 1, items => 1 );
+    if( $limit_table eq 'items' ) {
+        %tables = ( items => 1 );
     }
 
     }
 
-    # traverse through record
-  MARCFIELD: foreach my $field ( $record->fields() ) {
-        my $tag = $field->tag();
-        next MARCFIELD unless exists $inverted_field_map->{$tag};
-        if ( $field->is_control_field() ) {
-            my $kohafields = $inverted_field_map->{$tag}->{list};
-          ENTRY: foreach my $entry ( @{$kohafields} ) {
-                my ( $subfield, $table, $column ) = @{$entry};
-                my $value = $field->data;
-                next ENTRY unless exists $tables{$table};
-                next ENTRY if !$value;
-                my $key = _disambiguate( $table, $column );
-                if ( $result->{$key} ) {
-                    $result->{$key} .= " | " . $value
-                        unless $result->{$key} eq $value;
-                } else {
-                    $result->{$key} = $value;
-                }
-            }
-        } else {
-
-            # deal with subfields
-          MARCSUBFIELD: foreach my $sf ( $field->subfields() ) {
-                my $code = $sf->[0];
-                next MARCSUBFIELD unless exists $inverted_field_map->{$tag}->{sfs}->{$code};
-                my $value = $sf->[1];
-              SFENTRY: foreach my $entry ( @{ $inverted_field_map->{$tag}->{sfs}->{$code} } ) {
-                    my ( $table, $column ) = @{$entry};
-                    next SFENTRY unless exists $tables{$table};
-                    next SFENTRY if !$value;
-                    my $key = _disambiguate( $table, $column );
-                    if ( $result->{$key} ) {
-                        $result->{$key} .= " | " . $value
-                            unless $result->{$key} eq $value;
-                    } else {
-                        $result->{$key} = $value;
-                    }
-                }
-            }
-        }
-    }
-
-    # modify copyrightdate to keep only the 1st year found
-    if ( exists $result->{'copyrightdate'} ) {
-        my $temp = $result->{'copyrightdate'};
-        $temp =~ m/c(\d\d\d\d)/;
-        if ( $temp =~ m/c(\d\d\d\d)/ and $1 > 0 ) {    # search cYYYY first
-            $result->{'copyrightdate'} = $1;
-        } else {                                       # if no cYYYY, get the 1st date.
-            $temp =~ m/(\d\d\d\d)/;
-            $result->{'copyrightdate'} = $1;
-        }
-    }
-
-    # modify publicationyear to keep only the 1st year found
-    if ( exists $result->{'publicationyear'} ) {
-        my $temp = $result->{'publicationyear'};
-        if ( $temp =~ m/c(\d\d\d\d)/ and $1 > 0 ) {    # search cYYYY first
-            $result->{'publicationyear'} = $1;
-        } else {                                       # if no cYYYY, get the 1st date.
-            $temp =~ m/(\d\d\d\d)/;
-            $result->{'publicationyear'} = $1;
-        }
-    }
-
-    return $result;
-}
-
-sub _get_inverted_marc_field_map {
-    my ( $frameworkcode ) = @_;
-    my $field_map = {};
-    my $mss = GetMarcSubfieldStructure( $frameworkcode );
-
+    # 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 } ) {
     foreach my $kohafield ( keys %{ $mss } ) {
-        foreach my $fld ( @{ $mss->{$kohafield} } ) {
-            my $tag = $fld->{tagfield};
-            my $subfield = $fld->{tagsubfield};
-            my ( $table, $column ) = split /[.]/, $kohafield, 2;
-            push @{ $field_map->{$tag}->{list} },
-                [ $subfield, $table, $column ];
-            push @{ $field_map->{$tag}->{sfs}->{$subfield} },
-                [ $table, $column ];
-        }
+        my ( $table, $column ) = split /[.]/, $kohafield, 2;
+        next unless $tables{$table};
+        my $val = TransformMarcToKohaOneField( $kohafield, $record );
+        next if !defined $val;
+        my $key = _disambiguate( $table, $column );
+        $result->{$key} = $val;
     }
     }
-    return $field_map;
+    return $result;
 }
 
 =head2 _disambiguate
 }
 
 =head2 _disambiguate
@@ -2708,15 +2612,6 @@ more.
 
 =cut
 
 
 =cut
 
-sub CountItemsIssued {
-    my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare('SELECT COUNT(*) as issuedCount FROM items, issues WHERE items.itemnumber = issues.itemnumber AND items.biblionumber = ?');
-    $sth->execute($biblionumber);
-    my $row = $sth->fetchrow_hashref();
-    return $row->{'issuedCount'};
-}
-
 sub _disambiguate {
     my ( $table, $column ) = @_;
     if ( $column eq "cn_sort" or $column eq "cn_source" ) {
 sub _disambiguate {
     my ( $table, $column ) = @_;
     if ( $column eq "cn_sort" or $column eq "cn_source" ) {
@@ -2729,19 +2624,79 @@ sub _disambiguate {
 
 =head2 TransformMarcToKohaOneField
 
 
 =head2 TransformMarcToKohaOneField
 
-    $value = TransformMarcToKohaOneField( 'biblio.title', $record, $frameworkcode );
+    $val = TransformMarcToKohaOneField( 'biblio.title', $marc );
+
+    Note: The authoritative Default framework is used implicitly.
 
 =cut
 
 sub TransformMarcToKohaOneField {
 
 =cut
 
 sub TransformMarcToKohaOneField {
-    my ( $kohafield, $marc, $frameworkcode ) = @_;
-
-    # It is not really useful to repeat all code from TransformMarcToKoha here.
-    # Since it is fast enough (by caching), we just extract the field.
-    my $koharec = TransformMarcToKoha( $marc, $frameworkcode );
-    my @temp = split /\./, $kohafield, 2;
-    $kohafield = _disambiguate( @temp ) if @temp > 1;
-    return $koharec ? $koharec->{$kohafield} : undef;
+    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
+
+    Helper routine for TransformMarcToKohaOneField
+
+=cut
+
+sub _adjust_pubyear {
+    my $retval = shift;
+    # modify return value to keep only the 1st year found
+    if( $retval =~ m/c(\d\d\d\d)/ and $1 > 0 ) { # search cYYYY first
+        $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) ));
+    }
+    return $retval;
+}
+
+=head2 CountItemsIssued
+
+    my $count = CountItemsIssued( $biblionumber );
+
+=cut
+
+sub CountItemsIssued {
+    my ($biblionumber) = @_;
+    my $dbh            = C4::Context->dbh;
+    my $sth            = $dbh->prepare('SELECT COUNT(*) as issuedCount FROM items, issues WHERE items.itemnumber = issues.itemnumber AND items.biblionumber = ?');
+    $sth->execute($biblionumber);
+    my $row = $sth->fetchrow_hashref();
+    return $row->{'issuedCount'};
 }
 
 =head2 ModZebra
 }
 
 =head2 ModZebra
@@ -2816,7 +2771,11 @@ sub ModZebra {
 
 =head2 EmbedItemsInMarcBiblio
 
 
 =head2 EmbedItemsInMarcBiblio
 
-    EmbedItemsInMarcBiblio($marc, $biblionumber, $itemnumbers, $opac);
+    EmbedItemsInMarcBiblio({
+        marc_record  => $marc,
+        biblionumber => $biblionumber,
+        item_numbers => $itemnumbers,
+        opac         => $opac });
 
 Given a MARC::Record object containing a bib record,
 modify it to include the items attached to it as 9XX
 
 Given a MARC::Record object containing a bib record,
 modify it to include the items attached to it as 9XX
@@ -2825,14 +2784,23 @@ if $itemnumbers is defined, only specified itemnumbers are embedded.
 
 If $opac is true, then opac-relevant suppressions are included.
 
 
 If $opac is true, then opac-relevant suppressions are included.
 
+If opac filtering will be done, borcat should be passed to properly
+override if necessary.
+
 =cut
 
 sub EmbedItemsInMarcBiblio {
 =cut
 
 sub EmbedItemsInMarcBiblio {
-    my ($marc, $biblionumber, $itemnumbers, $opac) = @_;
+    my ($params) = @_;
+    my ($marc, $biblionumber, $itemnumbers, $opac, $borcat);
+    $marc = $params->{marc_record};
     if ( !$marc ) {
         carp 'EmbedItemsInMarcBiblio: No MARC record passed';
         return;
     }
     if ( !$marc ) {
         carp 'EmbedItemsInMarcBiblio: No MARC record passed';
         return;
     }
+    $biblionumber = $params->{biblionumber};
+    $itemnumbers = $params->{item_numbers};
+    $opac = $params->{opac};
+    $borcat = $params->{borcat} // q{};
 
     $itemnumbers = [] unless defined $itemnumbers;
 
 
     $itemnumbers = [] unless defined $itemnumbers;
 
@@ -2843,20 +2811,32 @@ sub EmbedItemsInMarcBiblio {
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
     $sth->execute($biblionumber);
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
     $sth->execute($biblionumber);
-    my @item_fields;
     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
-    my @items;
+
+    my @item_fields; # Array holding the actual MARC data for items to be included.
+    my @items;       # Array holding items which are both in the list (sitenumbers)
+                     # and on this biblionumber
+
+    # Flag indicating if there is potential hiding.
     my $opachiddenitems = $opac
       && ( C4::Context->preference('OpacHiddenItems') !~ /^\s*$/ );
     my $opachiddenitems = $opac
       && ( C4::Context->preference('OpacHiddenItems') !~ /^\s*$/ );
+
     require C4::Items;
     while ( my ($itemnumber) = $sth->fetchrow_array ) {
         next if @$itemnumbers and not grep { $_ == $itemnumber } @$itemnumbers;
     require C4::Items;
     while ( my ($itemnumber) = $sth->fetchrow_array ) {
         next if @$itemnumbers and not grep { $_ == $itemnumber } @$itemnumbers;
-        my $i = $opachiddenitems ? C4::Items::GetItem($itemnumber) : undef;
-        push @items, { itemnumber => $itemnumber, item => $i };
+        my $item;
+        if ( $opachiddenitems ) {
+            $item = Koha::Items->find($itemnumber);
+            $item = $item ? $item->unblessed : undef;
+        }
+        push @items, { itemnumber => $itemnumber, item => $item };
     }
     }
+    my @items2pass = map { $_->{item} } @items;
     my @hiddenitems =
       $opachiddenitems
     my @hiddenitems =
       $opachiddenitems
-      ? C4::Items::GetHiddenItemnumbers( map { $_->{item} } @items )
+      ? C4::Items::GetHiddenItemnumbers({
+            items  => \@items2pass,
+            borcat => $borcat })
       : ();
     # Convert to a hash for quick searching
     my %hiddenitems = map { $_ => 1 } @hiddenitems;
       : ();
     # Convert to a hash for quick searching
     my %hiddenitems = map { $_ => 1 } @hiddenitems;
@@ -3014,7 +2994,7 @@ sub _koha_modify_biblio {
 
     $sth->execute(
         $frameworkcode,      $biblio->{'author'},      $biblio->{'title'},         $biblio->{'unititle'}, $biblio->{'notes'},
 
     $sth->execute(
         $frameworkcode,      $biblio->{'author'},      $biblio->{'title'},         $biblio->{'unititle'}, $biblio->{'notes'},
-        $biblio->{'serial'}, $biblio->{'seriestitle'}, $biblio->{'copyrightdate'}, $biblio->{'abstract'}, $biblio->{'biblionumber'}
+        $biblio->{'serial'}, $biblio->{'seriestitle'}, $biblio->{'copyrightdate'} ? int($biblio->{'copyrightdate'}) : undef, $biblio->{'abstract'}, $biblio->{'biblionumber'}
     ) if $biblio->{'biblionumber'};
 
     if ( $dbh->errstr || !$biblio->{'biblionumber'} ) {
     ) if $biblio->{'biblionumber'};
 
     if ( $dbh->errstr || !$biblio->{'biblionumber'} ) {
@@ -3273,8 +3253,8 @@ sub _koha_delete_biblio_metadata {
     $schema->txn_do(
         sub {
             $dbh->do( q|
     $schema->txn_do(
         sub {
             $dbh->do( q|
-                INSERT INTO deletedbiblio_metadata (biblionumber, format, marcflavour, metadata)
-                SELECT biblionumber, format, marcflavour, metadata FROM biblio_metadata WHERE biblionumber=?
+                INSERT INTO deletedbiblio_metadata (biblionumber, format, `schema`, metadata)
+                SELECT biblionumber, format, `schema`, metadata FROM biblio_metadata WHERE biblionumber=?
             |,  undef, $biblionumber );
             $dbh->do( q|DELETE FROM biblio_metadata WHERE biblionumber=?|,
                 undef, $biblionumber );
             |,  undef, $biblionumber );
             $dbh->do( q|DELETE FROM biblio_metadata WHERE biblionumber=?|,
                 undef, $biblionumber );
@@ -3346,17 +3326,28 @@ sub ModBiblioMarc {
     my $metadata = {
         biblionumber => $biblionumber,
         format       => 'marcxml',
     my $metadata = {
         biblionumber => $biblionumber,
         format       => 'marcxml',
-        marcflavour  => C4::Context->preference('marcflavour'),
+        schema       => C4::Context->preference('marcflavour'),
     };
     };
-    # FIXME To replace with ->find_or_create?
-    if ( my $m_rs = Koha::Biblio::Metadatas->find($metadata) ) {
-        $m_rs->metadata( $record->as_xml_record($encoding) );
-        $m_rs->store;
-    } else {
-        my $m_rs = Koha::Biblio::Metadata->new($metadata);
-        $m_rs->metadata( $record->as_xml_record($encoding) );
-        $m_rs->store;
+    $record->as_usmarc; # Bug 20126/10455 This triggers field length calculation
+
+    my $m_rs = Koha::Biblio::Metadatas->find($metadata) //
+        Koha::Biblio::Metadata->new($metadata);
+
+    my $userenv = C4::Context->userenv;
+    if ($userenv) {
+        my $borrowernumber = $userenv->{number};
+        my $borrowername = join ' ', @$userenv{qw(firstname surname)};
+        unless ($m_rs->in_storage) {
+            Koha::Util::MARC::set_marc_field($record, C4::Context->preference('MarcFieldForCreatorId'), $borrowernumber);
+            Koha::Util::MARC::set_marc_field($record, C4::Context->preference('MarcFieldForCreatorName'), $borrowername);
+        }
+        Koha::Util::MARC::set_marc_field($record, C4::Context->preference('MarcFieldForModifierId'), $borrowernumber);
+        Koha::Util::MARC::set_marc_field($record, C4::Context->preference('MarcFieldForModifierName'), $borrowername);
     }
     }
+
+    $m_rs->metadata( $record->as_xml_record($encoding) );
+    $m_rs->store;
+
     ModZebra( $biblionumber, "specialUpdate", "biblioserver", $record );
     return $biblionumber;
 }
     ModZebra( $biblionumber, "specialUpdate", "biblioserver", $record );
     return $biblionumber;
 }