Bug 17801: Use issuedate for limits in Most Circulated Items
[srvgit] / C4 / Biblio.pm
index fc23bb5..97fa95a 100644 (file)
@@ -30,7 +30,6 @@ BEGIN {
         AddBiblio
         GetBiblioData
         GetMarcBiblio
-        GetRecordValue
         GetISBDView
         GetMarcControlnumber
         GetMarcNotes
@@ -39,11 +38,9 @@ BEGIN {
         GetMarcSubjects
         GetMarcAuthors
         GetMarcSeries
-        GetMarcHosts
         GetMarcUrls
         GetUsedMarcStructure
         GetXmlBiblio
-        GetCOinSBiblio
         GetMarcPrice
         MungeMarcPrice
         GetMarcQuantity
@@ -56,7 +53,6 @@ BEGIN {
         TransformKohaToMarc
         PrepHostMarcField
         CountItemsIssued
-        CountBiblioInOrders
         ModBiblio
         ModZebra
         UpdateTotalIssues
@@ -80,6 +76,7 @@ BEGIN {
 }
 
 use Carp;
+use Try::Tiny;
 
 use Encode qw( decode is_utf8 );
 use List::MoreUtils qw( uniq );
@@ -104,6 +101,7 @@ use Koha::Acquisition::Currencies;
 use Koha::Biblio::Metadatas;
 use Koha::Holds;
 use Koha::ItemTypes;
+use Koha::Plugins;
 use Koha::SearchEngine;
 use Koha::Libraries;
 use Koha::Util::MARC;
@@ -234,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
@@ -256,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;
@@ -272,7 +276,7 @@ sub ModBiblio {
         logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
     }
 
-    if (C4::Context->preference('BiblioAddsAuthorities')) {
+    if ( !$disable_autolink && C4::Context->preference('BiblioAddsAuthorities') ) {
         BiblioAutoLink( $record, $frameworkcode );
     }
 
@@ -315,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);
@@ -336,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()
@@ -379,13 +385,6 @@ 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 $holds = $biblio->holds;
     while ( my $hold = $holds->next ) {
@@ -415,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;
@@ -492,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
@@ -504,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() }++;
@@ -524,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' ) {
@@ -533,16 +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"
-                            && C4::Heading::valid_bib_heading_subfield(
-                                $authority_type->auth_tag_to_report, $_->[0] )
-                            );
-                    } $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
@@ -624,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);
@@ -729,7 +696,7 @@ sub GetISBDView {
     my $sysprefname = $template eq 'opac' ? 'opacisbd' : 'isbd';
     my $framework = $params->{framework};
     my $itemtype  = $framework;
-    my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch", $itemtype );
+    my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch" );
     my $tagslib = GetMarcStructure( 1, $itemtype, { unsafe => 1 } );
 
     my $ISBD = C4::Context->preference($sysprefname);
@@ -855,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
@@ -893,25 +860,26 @@ sub GetMarcStructure {
 
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare(
-        "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable,ind1_defaultvalue,ind2_defaultvalue
+        "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, $ind1_defaultvalue, $ind2_defaultvalue );
+    my ( $liblibrarian, $libopac, $tag, $res, $tab, $mandatory, $repeatable, $important, $ind1_defaultvalue, $ind2_defaultvalue );
 
-    while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable, $ind1_defaultvalue, $ind2_defaultvalue ) = $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
@@ -935,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;
@@ -1181,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"; }
@@ -1228,158 +1197,6 @@ sub GetXmlBiblio {
     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.
@@ -1627,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;
         }
     }
@@ -1677,7 +1494,7 @@ sub GetMarcISSN {
 =cut
 
 sub GetMarcNotes {
-    my ( $record, $marcflavour ) = @_;
+    my ( $record, $marcflavour, $opac ) = @_;
     if (!$record) {
         carp 'GetMarcNotes called on undefined record';
         return;
@@ -1685,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
@@ -2032,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);
@@ -2154,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( '', { unsafe => 1 } ); # do not change framewok
+    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} //= [];
@@ -2184,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 )
@@ -2296,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;
@@ -2323,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 )
@@ -2365,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;
@@ -2537,6 +2324,7 @@ sub TransformHtmlToMarc {
         }
     }
 
+    @fields = sort { $a->tag() cmp $b->tag() } @fields;
     $record->append_fields(@fields);
     return $record;
 }
@@ -2709,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
 
@@ -2735,7 +2523,6 @@ sub ModZebra {
                     biblionumber => $biblionumber,
                     embed_items  => 1 });
             }
-            my $records = [$record];
             $indexer->update_index_background( [$biblionumber], [$record] );
         }
         elsif ( $op eq 'recordDelete' ) {
@@ -2811,7 +2598,7 @@ sub EmbedItemsInMarcBiblio {
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
     $sth->execute($biblionumber);
-    my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
+    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)
@@ -2863,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 ) {
@@ -2894,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'} );
@@ -2938,6 +2725,10 @@ sub _koha_add_biblio {
         SET frameworkcode = ?,
             author = ?,
             title = ?,
+            subtitle = ?,
+            medium = ?,
+            part_number = ?,
+            part_name = ?,
             unititle =?,
             notes = ?,
             serial = ?,
@@ -2948,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'};
@@ -2981,6 +2774,10 @@ sub _koha_modify_biblio {
         SET    frameworkcode = ?,
                author = ?,
                title = ?,
+               subtitle = ?,
+               medium = ?,
+               part_number = ?,
+               part_name = ?,
                unititle = ?,
                notes = ?,
                serial = ?,
@@ -2993,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'} ? int($biblio->{'copyrightdate'}) : undef, $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'} ) {
@@ -3336,7 +3135,7 @@ sub ModBiblioMarc {
     my $userenv = C4::Context->userenv;
     if ($userenv) {
         my $borrowernumber = $userenv->{number};
-        my $borrowername = join ' ', @$userenv{qw(firstname surname)};
+        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);
@@ -3349,27 +3148,8 @@ sub ModBiblioMarc {
     $m_rs->store;
 
     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
-    return $biblionumber;
-}
-
-=head2 CountBiblioInOrders
-
-    $count = &CountBiblioInOrders( $biblionumber);
-
-This function return count of biblios in orders with $biblionumber 
-
-=cut
 
-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
@@ -3531,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
     }
@@ -3605,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