Bug 26464: Code correction in opac-main when news_id passed
[koha-ffzg.git] / C4 / Biblio.pm
index 5749bba..97fa95a 100644 (file)
@@ -20,7 +20,63 @@ package C4::Biblio;
 # 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
+        GetISBDView
+        GetMarcControlnumber
+        GetMarcNotes
+        GetMarcISBN
+        GetMarcISSN
+        GetMarcSubjects
+        GetMarcAuthors
+        GetMarcSeries
+        GetMarcUrls
+        GetUsedMarcStructure
+        GetXmlBiblio
+        GetMarcPrice
+        MungeMarcPrice
+        GetMarcQuantity
+        GetAuthorisedValueDesc
+        GetMarcStructure
+        IsMarcStructureInternal
+        GetMarcFromKohaField
+        GetMarcSubfieldStructureFromKohaField
+        GetFrameworkCode
+        TransformKohaToMarc
+        PrepHostMarcField
+        CountItemsIssued
+        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 Try::Tiny;
 
 use Encode qw( decode is_utf8 );
 use List::MoreUtils qw( uniq );
@@ -42,101 +98,16 @@ use C4::Debug;
 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::Plugins;
 use Koha::SearchEngine;
 use Koha::Libraries;
+use Koha::Util::MARC;
 
-use vars qw(@ISA @EXPORT);
 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
-
-      &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
 
@@ -234,6 +205,10 @@ sub AddBiblio {
         $defer_marc_save = 1;
     }
 
+    if (C4::Context->preference('BiblioAddsAuthorities')) {
+        BiblioAutoLink( $record, $frameworkcode );
+    }
+
     my ( $biblionumber, $biblioitemnumber, $error );
     my $dbh = C4::Context->dbh;
 
@@ -257,13 +232,15 @@ sub AddBiblio {
         C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
     }
 
+    _after_biblio_action_hooks({ action => 'create', biblio_id => $biblionumber });
+
     logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
     return ( $biblionumber, $biblioitemnumber );
 }
 
 =head2 ModBiblio
 
-  ModBiblio( $record,$biblionumber,$frameworkcode);
+  ModBiblio( $record,$biblionumber,$frameworkcode, $disable_autolink);
 
 Replace an existing bib record identified by C<$biblionumber>
 with one supplied by the MARC::Record object C<$record>.  The embedded
@@ -279,12 +256,16 @@ 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.
 
+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.
+
 Returns 1 on success 0 on failure
 
 =cut
 
 sub ModBiblio {
-    my ( $record, $biblionumber, $frameworkcode ) = @_;
+    my ( $record, $biblionumber, $frameworkcode, $disable_autolink ) = @_;
     if (!$record) {
         carp 'No record passed to ModBiblio';
         return 0;
@@ -295,6 +276,10 @@ sub ModBiblio {
         logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
     }
 
+    if ( !$disable_autolink && 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()) {
@@ -334,6 +319,8 @@ sub ModBiblio {
     _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
     _koha_modify_biblioitem_nonmarc( $dbh, $oldbiblio );
 
+    _after_biblio_action_hooks({ action => 'modify', biblio_id => $biblionumber });
+
     # update OAI-PMH sets
     if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
         C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
@@ -355,7 +342,7 @@ sub _strip_item_fields {
     my $record = shift;
     my $frameworkcode = shift;
     # get the items before and append them to the biblio before updating the record, atm we just have the biblio
-    my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
+    my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber" );
 
     # delete any item fields from incoming record to avoid
     # duplication or incorrect data - use AddItem() or ModItem()
@@ -380,6 +367,10 @@ C<$error> : undef unless an error occurs
 
 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
 
@@ -394,15 +385,7 @@ sub DelBiblio {
 
     return $error if $error;
 
-    # We delete attached subscriptions
-    require C4::Serials;
-    my $subscriptions = C4::Serials::GetFullSubscriptionsFromBiblionumber($biblionumber);
-    foreach my $subscription (@$subscriptions) {
-        C4::Serials::DelSubscription( $subscription->{subscriptionid} );
-    }
-
     # We delete any existing holds
-    my $biblio = Koha::Biblios->find( $biblionumber );
     my $holds = $biblio->holds;
     while ( my $hold = $holds->next ) {
         $hold->cancel;
@@ -431,6 +414,8 @@ sub DelBiblio {
     # from being generated by _koha_delete_biblioitems
     $error = _koha_delete_biblio( $dbh, $biblionumber );
 
+    _after_biblio_action_hooks({ action => 'delete', biblio_id => $biblionumber });
+
     logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
 
     return;
@@ -508,7 +493,7 @@ sub LinkBibHeadingsToAuthorities {
     $allowrelink = 1 unless defined $allowrelink;
     my $num_headings_changed = 0;
     foreach my $field ( $bib->fields() ) {
-        my $heading = C4::Heading->new_from_bib_field( $field, $frameworkcode );
+        my $heading = C4::Heading->new_from_field( $field, $frameworkcode );
         next unless defined $heading;
 
         # check existing $9
@@ -520,7 +505,7 @@ sub LinkBibHeadingsToAuthorities {
             next;
         }
 
-        my ( $authid, $fuzzy ) = $linker->get_link($heading);
+        my ( $authid, $fuzzy, $match_count ) = $linker->get_link($heading);
         if ($authid) {
             $results{ $fuzzy ? 'fuzzy' : 'linked' }
               ->{ $heading->display_form() }++;
@@ -540,7 +525,7 @@ sub LinkBibHeadingsToAuthorities {
                 if ( _check_valid_auth_link( $current_link, $field ) ) {
                     $results{'linked'}->{ $heading->display_form() }++;
                 }
-                else {
+                elsif ( !$match_count ) {
                     my $authority_type = Koha::Authority::Types->find( $heading->auth_type() );
                     my $marcrecordauth = MARC::Record->new();
                     if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
@@ -549,13 +534,22 @@ sub LinkBibHeadingsToAuthorities {
                     }
                     $field->delete_subfield( code => '9' )
                       if defined $current_link;
-                    my $authfield =
-                      MARC::Field->new( $authority_type->auth_tag_to_report,
-                        '', '', "a" => "" . $field->subfield('a') );
-                    map {
-                        $authfield->add_subfields( $_->[0] => $_->[1] )
-                          if ( $_->[0] =~ /[A-z]/ && $_->[0] ne "a" )
-                    } $field->subfields();
+                    my @auth_subfields;
+                    foreach my $subfield ( $field->subfields() ){
+                        if ( $subfield->[0] =~ /[A-z]/
+                            && C4::Heading::valid_heading_subfield(
+                                $field->tag, $subfield->[0] )
+                           ){
+                            push @auth_subfields, $subfield->[0] => $subfield->[1];
+                        }
+                    }
+                    # Bib headings contain some ending punctuation that should NOT
+                    # be included in the authority record. Strip those before creation
+                    next unless @auth_subfields; # Don't try to create a record if we have no fields;
+                    my $last_sub = pop @auth_subfields;
+                    $last_sub =~ s/[\s]*[,.:=;!%\/][\s]*$//;
+                    push @auth_subfields, $last_sub;
+                    my $authfield = MARC::Field->new( $authority_type->auth_tag_to_report, '', '', @auth_subfields );
                     $marcrecordauth->insert_fields_ordered($authfield);
 
 # bug 2317: ensure new authority knows it's using UTF-8; currently
@@ -637,53 +631,13 @@ safest place.
 
 sub _check_valid_auth_link {
     my ( $authid, $field ) = @_;
-
     require C4::AuthoritiesMarc;
 
     my $authorized_heading =
       C4::AuthoritiesMarc::GetAuthorizedHeading( { 'authid' => $authid } ) || '';
-
    return ($field->as_string('abcdefghijklmnopqrstuvwxyz') eq $authorized_heading);
 }
 
-=head2 GetRecordValue
-
-  my $values = GetRecordValue($field, $record, $frameworkcode);
-
-Get MARC fields from a keyword defined in fieldmapping table.
-
-=cut
-
-sub GetRecordValue {
-    my ( $field, $record, $frameworkcode ) = @_;
-
-    if (!$record) {
-        carp 'GetRecordValue called with undefined record';
-        return;
-    }
-    my $dbh = C4::Context->dbh;
-
-    my $sth = $dbh->prepare('SELECT fieldcode, subfieldcode FROM fieldmapping WHERE frameworkcode = ? AND field = ?');
-    $sth->execute( $frameworkcode, $field );
-
-    my @result = ();
-
-    while ( my $row = $sth->fetchrow_hashref ) {
-        foreach my $field ( $record->field( $row->{fieldcode} ) ) {
-            if ( ( $row->{subfieldcode} ne "" && $field->subfield( $row->{subfieldcode} ) ) ) {
-                foreach my $subfield ( $field->subfield( $row->{subfieldcode} ) ) {
-                    push @result, { 'subfield' => $subfield };
-                }
-
-            } elsif ( $row->{subfieldcode} eq "" ) {
-                push @result, { 'subfield' => $field->as_string() };
-            }
-        }
-    }
-
-    return \@result;
-}
-
 =head2 GetBiblioData
 
   $data = &GetBiblioData($biblionumber);
@@ -742,8 +696,8 @@ sub GetISBDView {
     my $sysprefname = $template eq 'opac' ? 'opacisbd' : 'isbd';
     my $framework = $params->{framework};
     my $itemtype  = $framework;
-    my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch", $itemtype );
-    my $tagslib = &GetMarcStructure( 1, $itemtype, { unsafe => 1 } );
+    my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch" );
+    my $tagslib = GetMarcStructure( 1, $itemtype, { unsafe => 1 } );
 
     my $ISBD = C4::Context->preference($sysprefname);
     my $bloc = $ISBD;
@@ -868,7 +822,7 @@ sub GetISBDView {
         # Process subfield
     }
 
-GetMarcStructure creates keys (lib, tab, mandatory, repeatable) for a display purpose.
+GetMarcStructure creates keys (lib, tab, mandatory, repeatable, important) for a display purpose.
 These different values should not be processed as valid subfields.
 
 =cut
@@ -906,23 +860,26 @@ sub GetMarcStructure {
 
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare(
-        "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable 
+        "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable,important,ind1_defaultvalue,ind2_defaultvalue
         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, $important, $ind1_defaultvalue, $ind2_defaultvalue );
 
-    while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable ) = $sth->fetchrow ) {
+    while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable, $important, $ind1_defaultvalue, $ind2_defaultvalue ) = $sth->fetchrow ) {
         $res->{$tag}->{lib}        = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
         $res->{$tag}->{tab}        = "";
         $res->{$tag}->{mandatory}  = $mandatory;
+        $res->{$tag}->{important}  = $important;
         $res->{$tag}->{repeatable} = $repeatable;
+    $res->{$tag}->{ind1_defaultvalue} = $ind1_defaultvalue;
+    $res->{$tag}->{ind2_defaultvalue} = $ind2_defaultvalue;
     }
 
     $sth = $dbh->prepare(
-        "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab,mandatory,repeatable,authorised_value,authtypecode,value_builder,kohafield,seealso,hidden,isurl,link,defaultvalue,maxlength
+        "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab,mandatory,repeatable,authorised_value,authtypecode,value_builder,kohafield,seealso,hidden,isurl,link,defaultvalue,maxlength,important
          FROM   marc_subfield_structure 
          WHERE  frameworkcode=? 
          ORDER BY tagfield,tagsubfield
@@ -946,13 +903,14 @@ sub GetMarcStructure {
     while (
         (   $tag,          $subfield,      $liblibrarian, $libopac, $tab,    $mandatory, $repeatable, $authorised_value,
             $authtypecode, $value_builder, $kohafield,    $seealso, $hidden, $isurl,     $link,       $defaultvalue,
-            $maxlength
+            $maxlength, $important
         )
         = $sth->fetchrow
       ) {
         $res->{$tag}->{$subfield}->{lib}              = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
         $res->{$tag}->{$subfield}->{tab}              = $tab;
         $res->{$tag}->{$subfield}->{mandatory}        = $mandatory;
+        $res->{$tag}->{$subfield}->{important}        = $important;
         $res->{$tag}->{$subfield}->{repeatable}       = $repeatable;
         $res->{$tag}->{$subfield}->{authorised_value} = $authorised_value;
         $res->{$tag}->{$subfield}->{authtypecode}     = $authtypecode;
@@ -998,18 +956,54 @@ sub GetUsedMarcStructure {
     return $sth->fetchall_arrayref( {} );
 }
 
+=pod
+
 =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 {
-    my ( $frameworkcode ) = @_;
+    my ( $frameworkcode, $params ) = @_;
 
     $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;
@@ -1053,7 +1047,7 @@ sub GetMarcFromKohaField {
     return unless $kohafield;
     # The next call uses the Default framework since it is AUTHORITATIVE
     # for all Koha to MARC mappings.
-    my $mss = GetMarcSubfieldStructure( '' ); # Do not change framework
+    my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
     my @retval;
     foreach( @{ $mss->{$kohafield} } ) {
         push @retval, $_->{tagfield}, $_->{tagsubfield};
@@ -1080,7 +1074,7 @@ sub GetMarcSubfieldStructureFromKohaField {
 
     # The next call uses the Default framework since it is AUTHORITATIVE
     # for all Koha to MARC mappings.
-    my $mss = GetMarcSubfieldStructure(''); # Do not change framework
+    my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
     return unless $mss->{$kohafield};
     return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
 }
@@ -1090,7 +1084,8 @@ sub GetMarcSubfieldStructureFromKohaField {
   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.
@@ -1114,6 +1109,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.
 
+=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
@@ -1129,6 +1130,7 @@ sub GetMarcBiblio {
     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';
@@ -1148,7 +1150,7 @@ sub GetMarcBiblio {
 
     if ($marcxml) {
         $record = eval {
-            MARC::Record::new_from_xml( $marcxml, "utf8",
+            MARC::Record::new_from_xml( $marcxml, "UTF-8",
                 C4::Context->preference('marcflavour') );
         };
         if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
@@ -1156,7 +1158,11 @@ sub GetMarcBiblio {
 
         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;
@@ -1185,164 +1191,12 @@ sub GetXmlBiblio {
         FROM biblio_metadata
         WHERE biblionumber=?
             AND format='marcxml'
-            AND marcflavour=?
+            AND `schema`=?
     |, undef, $biblionumber, C4::Context->preference('marcflavour')
     );
     return $marcxml;
 }
 
-=head2 GetCOinSBiblio
-
-  my $coins = GetCOinSBiblio($record);
-
-Returns the COinS (a span) which can be included in a biblio record
-
-=cut
-
-sub GetCOinSBiblio {
-    my $record = shift;
-
-    # get the coin format
-    if ( ! $record ) {
-        carp 'GetCOinSBiblio called with undefined record';
-        return;
-    }
-    my $pos7 = substr $record->leader(), 7, 1;
-    my $pos6 = substr $record->leader(), 6, 1;
-    my $mtx;
-    my $genre;
-    my ( $aulast, $aufirst ) = ( '', '' );
-    my $oauthors  = '';
-    my $title     = '';
-    my $subtitle  = '';
-    my $pubyear   = '';
-    my $isbn      = '';
-    my $issn      = '';
-    my $publisher = '';
-    my $pages     = '';
-    my $titletype = 'b';
-
-    # For the purposes of generating COinS metadata, LDR/06-07 can be
-    # considered the same for UNIMARC and MARC21
-    my $fmts6;
-    my $fmts7;
-    %$fmts6 = (
-                'a' => 'book',
-                'b' => 'manuscript',
-                'c' => 'book',
-                'd' => 'manuscript',
-                'e' => 'map',
-                'f' => 'map',
-                'g' => 'film',
-                'i' => 'audioRecording',
-                'j' => 'audioRecording',
-                'k' => 'artwork',
-                'l' => 'document',
-                'm' => 'computerProgram',
-                'o' => 'document',
-                'r' => 'document',
-            );
-    %$fmts7 = (
-                    'a' => 'journalArticle',
-                    's' => 'journal',
-              );
-
-    $genre = $fmts6->{$pos6} ? $fmts6->{$pos6} : 'book';
-
-    if ( $genre eq 'book' ) {
-            $genre = $fmts7->{$pos7} if $fmts7->{$pos7};
-    }
-
-    ##### We must transform mtx to a valable mtx and document type ####
-    if ( $genre eq 'book' ) {
-            $mtx = 'book';
-    } elsif ( $genre eq 'journal' ) {
-            $mtx = 'journal';
-            $titletype = 'j';
-    } elsif ( $genre eq 'journalArticle' ) {
-            $mtx   = 'journal';
-            $genre = 'article';
-            $titletype = 'a';
-    } else {
-            $mtx = 'dc';
-    }
-
-    $genre = ( $mtx eq 'dc' ) ? "&amp;rft.type=$genre" : "&amp;rft.genre=$genre";
-
-    if ( C4::Context->preference("marcflavour") eq "UNIMARC" ) {
-
-        # Setting datas
-        $aulast  = $record->subfield( '700', 'a' ) || '';
-        $aufirst = $record->subfield( '700', 'b' ) || '';
-        $oauthors = "&amp;rft.au=$aufirst $aulast";
-
-        # others authors
-        if ( $record->field('200') ) {
-            for my $au ( $record->field('200')->subfield('g') ) {
-                $oauthors .= "&amp;rft.au=$au";
-            }
-        }
-        $title =
-          ( $mtx eq 'dc' )
-          ? "&amp;rft.title=" . $record->subfield( '200', 'a' )
-          : "&amp;rft.title=" . $record->subfield( '200', 'a' ) . "&amp;rft.btitle=" . $record->subfield( '200', 'a' );
-        $pubyear   = $record->subfield( '210', 'd' ) || '';
-        $publisher = $record->subfield( '210', 'c' ) || '';
-        $isbn      = $record->subfield( '010', 'a' ) || '';
-        $issn      = $record->subfield( '011', 'a' ) || '';
-    } else {
-
-        # MARC21 need some improve
-
-        # Setting datas
-        if ( $record->field('100') ) {
-            $oauthors .= "&amp;rft.au=" . $record->subfield( '100', 'a' );
-        }
-
-        # others authors
-        if ( $record->field('700') ) {
-            for my $au ( $record->field('700')->subfield('a') ) {
-                $oauthors .= "&amp;rft.au=$au";
-            }
-        }
-        $title = "&amp;rft." . $titletype . "title=" . $record->subfield( '245', 'a' );
-        $subtitle = $record->subfield( '245', 'b' ) || '';
-        $title .= $subtitle;
-        if ($titletype eq 'a') {
-            $pubyear   = $record->field('008') || '';
-            $pubyear   = substr($pubyear->data(), 7, 4) if $pubyear;
-            $isbn      = $record->subfield( '773', 'z' ) || '';
-            $issn      = $record->subfield( '773', 'x' ) || '';
-            if ($mtx eq 'journal') {
-                $title    .= "&amp;rft.title=" . ( $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{} );
-            } else {
-                $title    .= "&amp;rft.btitle=" . ( $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{} );
-            }
-            foreach my $rel ($record->subfield( '773', 'g' )) {
-                if ($pages) {
-                    $pages .= ', ';
-                }
-                $pages .= $rel;
-            }
-        } else {
-            $pubyear   = $record->subfield( '260', 'c' ) || '';
-            $publisher = $record->subfield( '260', 'b' ) || '';
-            $isbn      = $record->subfield( '020', 'a' ) || '';
-            $issn      = $record->subfield( '022', 'a' ) || '';
-        }
-
-    }
-    my $coins_value =
-"ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3A$mtx$genre$title&amp;rft.isbn=$isbn&amp;rft.issn=$issn&amp;rft.aulast=$aulast&amp;rft.aufirst=$aufirst$oauthors&amp;rft.pub=$publisher&amp;rft.date=$pubyear&amp;rft.pages=$pages";
-    $coins_value =~ s/(\ |&[^a])/\+/g;
-    $coins_value =~ s/\"/\&quot\;/g;
-
-#<!-- TMPL_VAR NAME="ocoins_format" -->&amp;rft.au=<!-- TMPL_VAR NAME="author" -->&amp;rft.btitle=<!-- TMPL_VAR NAME="title" -->&amp;rft.date=<!-- TMPL_VAR NAME="publicationyear" -->&amp;rft.pages=<!-- TMPL_VAR NAME="pages" -->&amp;rft.isbn=<!-- TMPL_VAR NAME=amazonisbn -->&amp;rft.aucorp=&amp;rft.place=<!-- TMPL_VAR NAME="place" -->&amp;rft.pub=<!-- TMPL_VAR NAME="publishercode" -->&amp;rft.edition=<!-- TMPL_VAR NAME="edition" -->&amp;rft.series=<!-- TMPL_VAR NAME="series" -->&amp;rft.genre="
-
-    return $coins_value;
-}
-
-
 =head2 GetMarcPrice
 
 return the prices in accordance with the Marc format.
@@ -1514,7 +1368,8 @@ sub GetAuthorisedValueDesc {
 
         #---- 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
@@ -1589,7 +1444,7 @@ sub GetMarcISBN {
     my @marcisbns;
     foreach my $field ( $record->field($scope) ) {
         my $isbn = $field->subfield( 'a' );
-        if ( $isbn ne "" ) {
+        if ( $isbn && $isbn ne "" ) {
             push @marcisbns, $isbn;
         }
     }
@@ -1639,7 +1494,7 @@ sub GetMarcISSN {
 =cut
 
 sub GetMarcNotes {
-    my ( $record, $marcflavour ) = @_;
+    my ( $record, $marcflavour, $opac ) = @_;
     if (!$record) {
         carp 'GetMarcNotes called on undefined record';
         return;
@@ -1647,11 +1502,22 @@ sub GetMarcNotes {
 
     my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
     my @marcnotes;
-    my %blacklist = map { $_ => 1 }
-        split( /,/, C4::Context->preference('NotesBlacklist'));
+
+    #MARC21 specs indicate some notes should be private if first indicator 0
+    my %maybe_private = (
+        541 => 1,
+        542 => 1,
+        561 => 1,
+        583 => 1,
+        590 => 1
+    );
+
+    my %hiddenlist = map { $_ => 1 }
+        split( /,/, C4::Context->preference('NotesToHide'));
     foreach my $field ( $record->field($scope) ) {
         my $tag = $field->tag();
-        next if $blacklist{ $tag };
+        next if $hiddenlist{ $tag };
+        next if $opac && $maybe_private{$tag} && !$field->indicator(1);
         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
@@ -1994,53 +1860,6 @@ sub GetMarcSeries {
     return \@marcseries;
 }    #end getMARCseriess
 
-=head2 GetMarcHosts
-
-  $marchostsarray = GetMarcHosts($record,$marcflavour);
-
-Get all host records (773s MARC21, 461 UNIMARC) from the MARC record and returns them in an array.
-
-=cut
-
-sub GetMarcHosts {
-    my ( $record, $marcflavour ) = @_;
-    if (!$record) {
-        carp 'GetMarcHosts called on undefined record';
-        return;
-    }
-
-    my ( $tag,$title_subf,$bibnumber_subf,$itemnumber_subf);
-    $marcflavour ||="MARC21";
-    if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
-        $tag = "773";
-        $title_subf = "t";
-        $bibnumber_subf ="0";
-        $itemnumber_subf='9';
-    }
-    elsif ($marcflavour eq "UNIMARC") {
-        $tag = "461";
-        $title_subf = "t";
-        $bibnumber_subf ="0";
-        $itemnumber_subf='9';
-    };
-
-    my @marchosts;
-
-    foreach my $field ( $record->field($tag)) {
-
-        my @fields_loop;
-
-        my $hostbiblionumber = $field->subfield("$bibnumber_subf");
-        my $hosttitle = $field->subfield($title_subf);
-        my $hostitemnumber=$field->subfield($itemnumber_subf);
-        push @fields_loop, { hostbiblionumber => $hostbiblionumber, hosttitle => $hosttitle, hostitemnumber => $hostitemnumber};
-        push @marchosts, { MARCHOSTS_FIELDS_LOOP => \@fields_loop };
-
-        }
-    my $marchostsarray = \@marchosts;
-    return $marchostsarray;
-}
-
 =head2 UpsertMarcSubfield
 
     my $record = C4::Biblio::UpsertMarcSubfield($MARC::Record, $fieldTag, $subfieldCode, $subfieldContent);
@@ -2116,16 +1935,18 @@ sub TransformKohaToMarc {
 
     # In the next call we use the Default framework, since it is considered
     # authoritative for Koha to Marc mappings.
-    my $mss = GetMarcSubfieldStructure( '' ); # do not change framework
+    my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # do not change framework
     my $tag_hr = {};
     while ( my ($kohafield, $value) = each %$hash ) {
         foreach my $fld ( @{ $mss->{$kohafield} } ) {
             my $tagfield    = $fld->{tagfield};
             my $tagsubfield = $fld->{tagsubfield};
             next if !$tagfield;
-            my @values = $params->{no_split}
-                ? ( $value )
-                : split(/\s?\|\s?/, $value, -1);
+
+            # BZ 21800: split value if field is repeatable.
+            my @values = _check_split($params, $fld, $value)
+                ? split(/\s?\|\s?/, $value, -1)
+                : ( $value );
             foreach my $value ( @values ) {
                 next if $value eq '';
                 $tag_hr->{$tagfield} //= [];
@@ -2146,6 +1967,25 @@ sub TransformKohaToMarc {
     return $record;
 }
 
+sub _check_split {
+# Checks if $value must be split; may consult passed framework
+    my ($params, $fld, $value) = @_;
+    return if index($value,'|') == -1; # nothing to worry about
+    return if $params->{no_split};
+
+    # if we did not get a specific framework, check default in $mss
+    return $fld->{repeatable} if !$params->{framework};
+
+    # here we need to check the specific framework
+    my $mss = GetMarcSubfieldStructure($params->{framework}, { unsafe => 1 });
+    foreach my $fld2 ( @{ $mss->{ $fld->{kohafield} } } ) {
+        next if $fld2->{tagfield} ne $fld->{tagfield};
+        next if $fld2->{tagsubfield} ne $fld->{tagsubfield};
+        return 1 if $fld2->{repeatable};
+    }
+    return;
+}
+
 =head2 PrepHostMarcField
 
     $hostfield = PrepHostMarcField ( $hostbiblionumber,$hostitemnumber,$marcflavour )
@@ -2158,10 +1998,9 @@ sub PrepHostMarcField {
     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
     $marcflavour ||="MARC21";
     
-    require C4::Items;
     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" ) {
        
@@ -2184,7 +2023,7 @@ sub PrepHostMarcField {
 
        #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
@@ -2259,7 +2098,6 @@ sub TransformHtmlToXml {
     # MARC::Record->new_from_xml will fail (and Koha will die)
     my $unimarc_and_100_exist = 0;
     $unimarc_and_100_exist = 1 if $auth_type eq 'ITEM';    # if we rebuild an item, no need of a 100 field
-    my $prevvalue;
     my $prevtag = -1;
     my $first   = 1;
     my $j       = -1;
@@ -2286,16 +2124,9 @@ sub TransformHtmlToXml {
         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 ) };
-            my $ind1       = _default_ind_to_space($indicator1);
-            my $ind2;
-            if ( @$indicator[$j] ) {
-                $ind2 = _default_ind_to_space($indicator2);
-            } else {
-                warn "Indicator in @$tags[$i] is empty";
-                $ind2 = " ";
-            }
+            my $str = ( $indicator->[$j] // q{} ) . '  '; # extra space prevents substr outside of string warn
+            my $ind1 = _default_ind_to_space( substr( $str, 0, 1 ) );
+            my $ind2 = _default_ind_to_space( substr( $str, 1, 1 ) );
             if ( !$first ) {
                 $xml .= "</datafield>\n";
                 if (   ( @$tags[$i] && @$tags[$i] > 10 )
@@ -2328,19 +2159,12 @@ sub TransformHtmlToXml {
                 }
             }
         } else {    # @$tags[$i] eq $prevtag
-            my $indicator1 = eval { substr( @$indicator[$j], 0, 1 ) };
-            my $indicator2 = eval { substr( @$indicator[$j], 1, 1 ) };
-            my $ind1       = _default_ind_to_space($indicator1);
-            my $ind2;
-            if ( @$indicator[$j] ) {
-                $ind2 = _default_ind_to_space($indicator2);
-            } else {
-                warn "Indicator in @$tags[$i] is empty";
-                $ind2 = " ";
-            }
             if ( @$values[$i] eq "" ) {
             } else {
                 if ($first) {
+                    my $str = ( $indicator->[$j] // q{} ) . '  '; # extra space prevents substr outside of string warn
+                    my $ind1 = _default_ind_to_space( substr( $str, 0, 1 ) );
+                    my $ind2 = _default_ind_to_space( substr( $str, 1, 1 ) );
                     $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
                     $first = 0;
                     $close_last_tag = 1;
@@ -2367,7 +2191,6 @@ sub TransformHtmlToXml {
     }
     $xml .= "</record>\n";
     $xml .= MARC::File::XML::footer();
-    use Data::Printer colored => 1; warn p $xml;
     return $xml;
 }
 
@@ -2501,6 +2324,7 @@ sub TransformHtmlToMarc {
         }
     }
 
+    @fields = sort { $a->tag() cmp $b->tag() } @fields;
     $record->append_fields(@fields);
     return $record;
 }
@@ -2535,7 +2359,7 @@ sub TransformMarcToKoha {
 
     # The next call acknowledges Default as the authoritative framework
     # for Koha to MARC mappings.
-    my $mss = GetMarcSubfieldStructure(''); # Do not change framework
+    my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
     foreach my $kohafield ( keys %{ $mss } ) {
         my ( $table, $column ) = split /[.]/, $kohafield, 2;
         next unless $tables{$table};
@@ -2673,8 +2497,8 @@ $op is specialUpdate or recordDelete, and is used to know what we want to do
 
 $server is the server that we want to update
 
-$record is the update MARC record if it's available. If it's not supplied
-and is needed, it'll be loaded from the database.
+$record is the updated MARC record. If it's not supplied
+and is needed it will be loaded from the database.
 
 =cut
 
@@ -2699,7 +2523,6 @@ sub ModZebra {
                     biblionumber => $biblionumber,
                     embed_items  => 1 });
             }
-            my $records = [$record];
             $indexer->update_index_background( [$biblionumber], [$record] );
         }
         elsif ( $op eq 'recordDelete' ) {
@@ -2735,7 +2558,11 @@ sub ModZebra {
 
 =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
@@ -2744,14 +2571,23 @@ if $itemnumbers is defined, only specified itemnumbers are embedded.
 
 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 {
-    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;
     }
+    $biblionumber = $params->{biblionumber};
+    $itemnumbers = $params->{item_numbers};
+    $opac = $params->{opac};
+    $borcat = $params->{borcat} // q{};
 
     $itemnumbers = [] unless defined $itemnumbers;
 
@@ -2762,20 +2598,32 @@ sub EmbedItemsInMarcBiblio {
     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 @items;
+    my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber" );
+
+    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*$/ );
+
     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
-      ? 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;
@@ -2802,9 +2650,9 @@ the MARC XML.
 sub _koha_marc_update_bib_ids {
     my ( $record, $frameworkcode, $biblionumber, $biblioitemnumber ) = @_;
 
-    my ( $biblio_tag,     $biblio_subfield )     = GetMarcFromKohaField( "biblio.biblionumber",          $frameworkcode );
+    my ( $biblio_tag,     $biblio_subfield )     = GetMarcFromKohaField( "biblio.biblionumber" );
     die qq{No biblionumber tag for framework "$frameworkcode"} unless $biblio_tag;
-    my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.biblioitemnumber", $frameworkcode );
+    my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.biblioitemnumber" );
     die qq{No biblioitemnumber tag for framework "$frameworkcode"} unless $biblioitem_tag;
 
     if ( $biblio_tag < 10 ) {
@@ -2833,7 +2681,7 @@ sub _koha_marc_update_biblioitem_cn_sort {
     my $biblioitem    = shift;
     my $frameworkcode = shift;
 
-    my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.cn_sort", $frameworkcode );
+    my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.cn_sort" );
     return unless $biblioitem_tag;
 
     my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
@@ -2877,6 +2725,10 @@ sub _koha_add_biblio {
         SET frameworkcode = ?,
             author = ?,
             title = ?,
+            subtitle = ?,
+            medium = ?,
+            part_number = ?,
+            part_name = ?,
             unititle =?,
             notes = ?,
             serial = ?,
@@ -2887,8 +2739,10 @@ sub _koha_add_biblio {
         ";
     my $sth = $dbh->prepare($query);
     $sth->execute(
-        $frameworkcode, $biblio->{'author'},      $biblio->{'title'},         $biblio->{'unititle'}, $biblio->{'notes'},
-        $biblio->{'serial'},        $biblio->{'seriestitle'}, $biblio->{'copyrightdate'}, $biblio->{'abstract'}
+        $frameworkcode,        $biblio->{'author'},      $biblio->{'title'},       $biblio->{'subtitle'},
+        $biblio->{'medium'},   $biblio->{'part_number'}, $biblio->{'part_name'},   $biblio->{'unititle'},
+        $biblio->{'notes'},    $biblio->{'serial'},      $biblio->{'seriestitle'}, $biblio->{'copyrightdate'},
+        $biblio->{'abstract'}
     );
 
     my $biblionumber = $dbh->{'mysql_insertid'};
@@ -2920,6 +2774,10 @@ sub _koha_modify_biblio {
         SET    frameworkcode = ?,
                author = ?,
                title = ?,
+               subtitle = ?,
+               medium = ?,
+               part_number = ?,
+               part_name = ?,
                unititle = ?,
                notes = ?,
                serial = ?,
@@ -2932,8 +2790,10 @@ sub _koha_modify_biblio {
     my $sth = $dbh->prepare($query);
 
     $sth->execute(
-        $frameworkcode,      $biblio->{'author'},      $biblio->{'title'},         $biblio->{'unititle'}, $biblio->{'notes'},
-        $biblio->{'serial'}, $biblio->{'seriestitle'}, $biblio->{'copyrightdate'}, $biblio->{'abstract'}, $biblio->{'biblionumber'}
+        $frameworkcode,        $biblio->{'author'},      $biblio->{'title'},       $biblio->{'subtitle'},
+        $biblio->{'medium'},   $biblio->{'part_number'}, $biblio->{'part_name'},   $biblio->{'unititle'},
+        $biblio->{'notes'},    $biblio->{'serial'},      $biblio->{'seriestitle'}, $biblio->{'copyrightdate'} ? int($biblio->{'copyrightdate'}) : undef,
+        $biblio->{'abstract'}, $biblio->{'biblionumber'}
     ) if $biblio->{'biblionumber'};
 
     if ( $dbh->errstr || !$biblio->{'biblionumber'} ) {
@@ -3192,8 +3052,8 @@ sub _koha_delete_biblio_metadata {
     $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 );
@@ -3265,41 +3125,31 @@ sub ModBiblioMarc {
     my $metadata = {
         biblionumber => $biblionumber,
         format       => 'marcxml',
-        marcflavour  => C4::Context->preference('marcflavour'),
+        schema       => C4::Context->preference('marcflavour'),
     };
     $record->as_usmarc; # Bug 20126/10455 This triggers field length calculation
 
-    # 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;
-    }
-    ModZebra( $biblionumber, "specialUpdate", "biblioserver", $record );
-    return $biblionumber;
-}
+    my $m_rs = Koha::Biblio::Metadatas->find($metadata) //
+        Koha::Biblio::Metadata->new($metadata);
 
-=head2 CountBiblioInOrders
-
-    $count = &CountBiblioInOrders( $biblionumber);
+    my $userenv = C4::Context->userenv;
+    if ($userenv) {
+        my $borrowernumber = $userenv->{number};
+        my $borrowername = join ' ', map { $_ // q{} } @$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);
+    }
 
-This function return count of biblios in orders with $biblionumber 
+    $m_rs->metadata( $record->as_xml_record($encoding) );
+    $m_rs->store;
 
-=cut
+    ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
 
-sub CountBiblioInOrders {
- my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $query          = "SELECT count(*)
-          FROM  aqorders 
-          WHERE biblionumber=? AND (datecancellationprinted IS NULL OR datecancellationprinted='0000-00-00')";
-    my $sth = $dbh->prepare($query);
-    $sth->execute($biblionumber);
-    my $count = $sth->fetchrow;
-    return ($count);
+    return $biblionumber;
 }
 
 =head2 prepare_host_field
@@ -3461,7 +3311,7 @@ sub UpdateTotalIssues {
         return;
     }
     my $biblioitem = $biblio->biblioitem;
-    my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $biblio->frameworkcode);
+    my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField( 'biblioitems.totalissues' );
     unless ($totalissuestag) {
         return 1; # There is nothing to do
     }
@@ -3535,6 +3385,29 @@ sub RemoveAllNsb {
 1;
 
 
+=head2 _after_biblio_action_hooks
+
+Helper method that takes care of calling all plugin hooks
+
+=cut
+
+sub _after_biblio_action_hooks {
+    my ( $args ) = @_;
+
+    my $biblio_id = $args->{biblio_id};
+    my $action    = $args->{action};
+
+    my $biblio = Koha::Biblios->find( $biblio_id );
+    Koha::Plugins->call(
+        'after_biblio_action',
+        {
+            action    => $action,
+            biblio    => $biblio,
+            biblio_id => $biblio_id,
+        }
+    );
+}
+
 __END__
 
 =head1 AUTHOR