Bug 30952: Remove Poppins font, use Bootstrap 5 font stack instead
[koha-ffzg.git] / C4 / Biblio.pm
index e039fe7..006c359 100644 (file)
@@ -21,25 +21,20 @@ package C4::Biblio;
 
 use Modern::Perl;
 
 
 use Modern::Perl;
 
-use vars qw(@ISA @EXPORT);
+use vars qw(@ISA @EXPORT_OK);
 BEGIN {
     require Exporter;
     @ISA = qw(Exporter);
 
 BEGIN {
     require Exporter;
     @ISA = qw(Exporter);
 
-    @EXPORT = qw(
+    @EXPORT_OK = qw(
         AddBiblio
         GetBiblioData
         AddBiblio
         GetBiblioData
-        GetMarcBiblio
-        GetRecordValue
         GetISBDView
         GetMarcControlnumber
         GetISBDView
         GetMarcControlnumber
-        GetMarcNotes
         GetMarcISBN
         GetMarcISSN
         GetMarcSubjects
         GetMarcISBN
         GetMarcISSN
         GetMarcSubjects
-        GetMarcAuthors
         GetMarcSeries
         GetMarcSeries
-        GetMarcHosts
         GetMarcUrls
         GetUsedMarcStructure
         GetXmlBiblio
         GetMarcUrls
         GetUsedMarcStructure
         GetXmlBiblio
@@ -48,6 +43,7 @@ BEGIN {
         GetMarcQuantity
         GetAuthorisedValueDesc
         GetMarcStructure
         GetMarcQuantity
         GetAuthorisedValueDesc
         GetMarcStructure
+        GetMarcSubfieldStructure
         IsMarcStructureInternal
         GetMarcFromKohaField
         GetMarcSubfieldStructureFromKohaField
         IsMarcStructureInternal
         GetMarcFromKohaField
         GetMarcSubfieldStructureFromKohaField
@@ -55,64 +51,69 @@ BEGIN {
         TransformKohaToMarc
         PrepHostMarcField
         CountItemsIssued
         TransformKohaToMarc
         PrepHostMarcField
         CountItemsIssued
-        CountBiblioInOrders
         ModBiblio
         ModZebra
         ModBiblio
         ModZebra
+        EmbedItemsInMarcBiblio
         UpdateTotalIssues
         RemoveAllNsb
         DelBiblio
         BiblioAutoLink
         LinkBibHeadingsToAuthorities
         UpdateTotalIssues
         RemoveAllNsb
         DelBiblio
         BiblioAutoLink
         LinkBibHeadingsToAuthorities
+        ApplyMarcOverlayRules
         TransformMarcToKoha
         TransformHtmlToMarc
         TransformHtmlToXml
         prepare_host_field
         TransformMarcToKoha
         TransformHtmlToMarc
         TransformHtmlToXml
         prepare_host_field
-        SplitSubtitle
     );
 
     # 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 ;-)
     );
 
     # 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(
+    push @EXPORT_OK, qw(
       ModBiblioMarc
     );
 }
 
       ModBiblioMarc
     );
 }
 
-use Carp;
-use Try::Tiny;
+use Carp qw( carp );
+use Try::Tiny qw( catch try );
 
 
-use Encode qw( decode is_utf8 );
+use Encode;
 use List::MoreUtils qw( uniq );
 use MARC::Record;
 use MARC::File::USMARC;
 use MARC::File::XML;
 use List::MoreUtils qw( uniq );
 use MARC::Record;
 use MARC::File::USMARC;
 use MARC::File::XML;
-use POSIX qw(strftime);
-use Module::Load::Conditional qw(can_load);
+use POSIX qw( strftime );
+use Module::Load::Conditional qw( can_load );
 
 use C4::Koha;
 
 use C4::Koha;
-use C4::Log;    # logaction
+use C4::Log qw( logaction );    # logaction
 use C4::Budgets;
 use C4::Budgets;
-use C4::ClassSource;
-use C4::Charset;
+use C4::ClassSource qw( GetClassSort GetClassSource );
+use C4::Charset qw(
+    nsb_clean
+    SetMarcUnicodeFlag
+    SetUTF8Flag
+);
 use C4::Linker;
 use C4::OAI::Sets;
 use C4::Linker;
 use C4::OAI::Sets;
-use C4::Debug;
+use C4::Items qw( GetMarcItem );
 
 
+use Koha::Logger;
 use Koha::Caches;
 use Koha::Authority::Types;
 use Koha::Acquisition::Currencies;
 use Koha::Caches;
 use Koha::Authority::Types;
 use Koha::Acquisition::Currencies;
+use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
 use Koha::Biblio::Metadatas;
 use Koha::Holds;
 use Koha::ItemTypes;
 use Koha::Biblio::Metadatas;
 use Koha::Holds;
 use Koha::ItemTypes;
+use Koha::MarcOverlayRules;
 use Koha::Plugins;
 use Koha::SearchEngine;
 use Koha::Plugins;
 use Koha::SearchEngine;
+use Koha::SearchEngine::Indexer;
 use Koha::Libraries;
 use Koha::Util::MARC;
 
 use Koha::Libraries;
 use Koha::Util::MARC;
 
-use vars qw($debug $cgi_debug);
-
-
 =head1 NAME
 
 C4::Biblio - cataloging management functions
 =head1 NAME
 
 C4::Biblio - cataloging management functions
@@ -183,68 +184,130 @@ The first argument is a C<MARC::Record> object containing the
 bib to add, while the second argument is the desired MARC
 framework code.
 
 bib to add, while the second argument is the desired MARC
 framework code.
 
-This function also accepts a third, optional argument: a hashref
-to additional options.  The only defined option is C<defer_marc_save>,
-which if present and mapped to a true value, causes C<AddBiblio>
-to omit the call to save the MARC in C<biblio_metadata.metadata>
-This option is provided B<only>
-for the use of scripts such as C<bulkmarcimport.pl> that may need
-to do some manipulation of the MARC record for item parsing before
-saving it and which cannot afford the performance hit of saving
-the MARC record twice.  Consequently, do not use that option
-unless you can guarantee that C<ModBiblioMarc> will be called.
+The C<$options> argument is a hashref with additional parameters:
+
+=over 4
+
+=item B<defer_marc_save>: used when ModBiblioMarc is handled by the caller
+
+=item B<skip_record_index>: used when the indexing schedulling will be handled by the caller
+
+=back
 
 =cut
 
 sub AddBiblio {
 
 =cut
 
 sub AddBiblio {
-    my $record          = shift;
-    my $frameworkcode   = shift;
-    my $options         = @_ ? shift : undef;
-    my $defer_marc_save = 0;
+    my ( $record, $frameworkcode, $options ) = @_;
+
+    $options //= {};
+    my $skip_record_index = $options->{skip_record_index} || 0;
+    my $defer_marc_save   = $options->{defer_marc_save}   || 0;
+
     if (!$record) {
         carp('AddBiblio called with undefined record');
         return;
     }
     if (!$record) {
         carp('AddBiblio called with undefined record');
         return;
     }
-    if ( defined $options and exists $options->{'defer_marc_save'} and $options->{'defer_marc_save'} ) {
-        $defer_marc_save = 1;
-    }
-
-    if (C4::Context->preference('BiblioAddsAuthorities')) {
-        BiblioAutoLink( $record, $frameworkcode );
-    }
 
 
-    my ( $biblionumber, $biblioitemnumber, $error );
-    my $dbh = C4::Context->dbh;
+    my $schema = Koha::Database->schema;
+    my ( $biblionumber, $biblioitemnumber );
+    try {
+        $schema->txn_do(sub {
+
+            # transform the data into koha-table style data
+            SetUTF8Flag($record);
+            my $olddata = TransformMarcToKoha({ record => $record, limit_table => 'no_items' });
+
+            my $biblio = Koha::Biblio->new(
+                {
+                    frameworkcode => $frameworkcode,
+                    author        => $olddata->{author},
+                    title         => $olddata->{title},
+                    subtitle      => $olddata->{subtitle},
+                    medium        => $olddata->{medium},
+                    part_number   => $olddata->{part_number},
+                    part_name     => $olddata->{part_name},
+                    unititle      => $olddata->{unititle},
+                    notes         => $olddata->{notes},
+                    serial =>
+                      ( $olddata->{serial} || $olddata->{seriestitle} ? 1 : 0 ),
+                    seriestitle   => $olddata->{seriestitle},
+                    copyrightdate => $olddata->{copyrightdate},
+                    datecreated   => \'NOW()',
+                    abstract      => $olddata->{abstract},
+                }
+            )->store;
+            $biblionumber = $biblio->biblionumber;
+            Koha::Exceptions::ObjectNotCreated->throw unless $biblio;
+
+            my ($cn_sort) = GetClassSort( $olddata->{'biblioitems.cn_source'}, $olddata->{'cn_class'}, $olddata->{'cn_item'} );
+            my $biblioitem = Koha::Biblioitem->new(
+                {
+                    biblionumber          => $biblionumber,
+                    volume                => $olddata->{volume},
+                    number                => $olddata->{number},
+                    itemtype              => $olddata->{itemtype},
+                    isbn                  => $olddata->{isbn},
+                    issn                  => $olddata->{issn},
+                    publicationyear       => $olddata->{publicationyear},
+                    publishercode         => $olddata->{publishercode},
+                    volumedate            => $olddata->{volumedate},
+                    volumedesc            => $olddata->{volumedesc},
+                    collectiontitle       => $olddata->{collectiontitle},
+                    collectionissn        => $olddata->{collectionissn},
+                    collectionvolume      => $olddata->{collectionvolume},
+                    editionstatement      => $olddata->{editionstatement},
+                    editionresponsibility => $olddata->{editionresponsibility},
+                    illus                 => $olddata->{illus},
+                    pages                 => $olddata->{pages},
+                    notes                 => $olddata->{bnotes},
+                    size                  => $olddata->{size},
+                    place                 => $olddata->{place},
+                    lccn                  => $olddata->{lccn},
+                    url                   => $olddata->{url},
+                    cn_source      => $olddata->{'biblioitems.cn_source'},
+                    cn_class       => $olddata->{cn_class},
+                    cn_item        => $olddata->{cn_item},
+                    cn_suffix      => $olddata->{cn_suff},
+                    cn_sort        => $cn_sort,
+                    totalissues    => $olddata->{totalissues},
+                    ean            => $olddata->{ean},
+                    agerestriction => $olddata->{agerestriction},
+                }
+            )->store;
+            Koha::Exceptions::ObjectNotCreated->throw unless $biblioitem;
+            $biblioitemnumber = $biblioitem->biblioitemnumber;
 
 
-    # transform the data into koha-table style data
-    SetUTF8Flag($record);
-    my $olddata = TransformMarcToKoha( $record, $frameworkcode );
-    ( $biblionumber, $error ) = _koha_add_biblio( $dbh, $olddata, $frameworkcode );
-    $olddata->{'biblionumber'} = $biblionumber;
-    ( $biblioitemnumber, $error ) = _koha_add_biblioitem( $dbh, $olddata );
+            _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
 
 
-    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
+            # update MARC subfield that stores biblioitems.cn_sort
+            _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
 
 
-    # update MARC subfield that stores biblioitems.cn_sort
-    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
+            if (C4::Context->preference('AutoLinkBiblios')) {
+                BiblioAutoLink( $record, $frameworkcode );
+            }
 
 
-    # now add the record
-    ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
+            # now add the record
+            ModBiblioMarc( $record, $biblionumber, { skip_record_index => $skip_record_index } ) unless $defer_marc_save;
 
 
-    # update OAI-PMH sets
-    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
-        C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
-    }
+            # update OAI-PMH sets
+            if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
+                C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
+            }
 
 
-    _after_biblio_action_hooks({ action => 'create', biblio_id => $biblionumber });
+            _after_biblio_action_hooks({ action => 'create', biblio_id => $biblionumber });
 
 
-    logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
+            logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
+        });
+    } catch {
+        warn $_;
+        ( $biblionumber, $biblioitemnumber ) = ( undef, undef );
+    };
     return ( $biblionumber, $biblioitemnumber );
 }
 
 =head2 ModBiblio
 
     return ( $biblionumber, $biblioitemnumber );
 }
 
 =head2 ModBiblio
 
-  ModBiblio( $record,$biblionumber,$frameworkcode, $disable_autolink);
+  ModBiblio($record, $biblionumber, $frameworkcode, $options);
 
 Replace an existing bib record identified by C<$biblionumber>
 with one supplied by the MARC::Record object C<$record>.  The embedded
 
 Replace an existing bib record identified by C<$biblionumber>
 with one supplied by the MARC::Record object C<$record>.  The embedded
@@ -260,27 +323,50 @@ in the C<biblio> and C<biblioitems> tables, as well as
 which fields are used to store embedded item, biblioitem,
 and biblionumber data for indexing.
 
 which fields are used to store embedded item, biblioitem,
 and biblionumber data for indexing.
 
-Unless C<$disable_autolink> is passed ModBiblio will relink record headings
+The C<$options> argument is a hashref with additional parameters:
+
+=over 4
+
+=item C<overlay_context>
+
+This parameter is forwarded to L</ApplyMarcOverlayRules> where it is used for
+selecting the current rule set if MARCOverlayRules is enabled.
+See L</ApplyMarcOverlayRules> for more details.
+
+=item C<disable_autolink>
+
+Unless C<disable_autolink> is passed ModBiblio will relink record headings
 to authorities based on settings in the system preferences. This flag allows
 us to not relink records when the authority linker is saving modifications.
 
 to authorities based on settings in the system preferences. This flag allows
 us to not relink records when the authority linker is saving modifications.
 
+=item C<skip_holds_queue>
+
+Unless C<skip_holds_queue> is passed, ModBiblio will trigger the BatchUpdateBiblioHoldsQueue
+task to rebuild the holds queue for the biblio if I<RealTimeHoldsQueue> is enabled.
+
+=back
+
 Returns 1 on success 0 on failure
 
 =cut
 
 sub ModBiblio {
 Returns 1 on success 0 on failure
 
 =cut
 
 sub ModBiblio {
-    my ( $record, $biblionumber, $frameworkcode, $disable_autolink ) = @_;
+    my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
+
+    $options //= {};
+    my $skip_record_index = $options->{skip_record_index} || 0;
+
     if (!$record) {
         carp 'No record passed to ModBiblio';
         return 0;
     }
 
     if ( C4::Context->preference("CataloguingLog") ) {
     if (!$record) {
         carp 'No record passed to ModBiblio';
         return 0;
     }
 
     if ( C4::Context->preference("CataloguingLog") ) {
-        my $newrecord = GetMarcBiblio({ biblionumber => $biblionumber });
-        logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
+        my $biblio = Koha::Biblios->find($biblionumber);
+        logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $biblio->metadata->record->as_formatted );
     }
 
     }
 
-    if ( !$disable_autolink && C4::Context->preference('BiblioAddsAuthorities') ) {
+    if ( !$options->{disable_autolink} && C4::Context->preference('AutoLinkBiblios') ) {
         BiblioAutoLink( $record, $frameworkcode );
     }
 
         BiblioAutoLink( $record, $frameworkcode );
     }
 
@@ -301,6 +387,21 @@ sub ModBiblio {
 
     _strip_item_fields($record, $frameworkcode);
 
 
     _strip_item_fields($record, $frameworkcode);
 
+    # apply overlay rules
+    if (   C4::Context->preference('MARCOverlayRules')
+        && $biblionumber
+        && defined $options
+        && exists $options->{overlay_context} )
+    {
+        $record = ApplyMarcOverlayRules(
+            {
+                biblionumber    => $biblionumber,
+                record          => $record,
+                overlay_context => $options->{overlay_context},
+            }
+        );
+    }
+
     # update biblionumber and biblioitemnumber in MARC
     # FIXME - this is assuming a 1 to 1 relationship between
     # biblios and biblioitems
     # update biblionumber and biblioitemnumber in MARC
     # FIXME - this is assuming a 1 to 1 relationship between
     # biblios and biblioitems
@@ -311,13 +412,13 @@ sub ModBiblio {
     _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
 
     # load the koha-table data object
     _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
 
     # load the koha-table data object
-    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
+    my $oldbiblio = TransformMarcToKoha({ record => $record });
 
     # update MARC subfield that stores biblioitems.cn_sort
     _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
 
     # update the MARC record (that now contains biblio and items) with the new record data
 
     # update MARC subfield that stores biblioitems.cn_sort
     _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
 
     # update the MARC record (that now contains biblio and items) with the new record data
-    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
+    ModBiblioMarc( $record, $biblionumber, { skip_record_index => $skip_record_index } );
 
     # modify the other koha tables
     _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
 
     # modify the other koha tables
     _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
@@ -330,6 +431,12 @@ sub ModBiblio {
         C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
     }
 
         C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
     }
 
+    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+        {
+            biblio_ids => [ $biblionumber ]
+        }
+    ) unless $options->{skip_holds_queue} or !C4::Context->preference('RealTimeHoldsQueue');
+
     return 1;
 }
 
     return 1;
 }
 
@@ -358,7 +465,7 @@ sub _strip_item_fields {
 
 =head2 DelBiblio
 
 
 =head2 DelBiblio
 
-  my $error = &DelBiblio($biblionumber);
+  my $error = &DelBiblio($biblionumber, $params);
 
 Exported function (core API) for deleting a biblio in koha.
 Deletes biblio record from Zebra and Koha tables (biblio & biblioitems)
 
 Exported function (core API) for deleting a biblio in koha.
 Deletes biblio record from Zebra and Koha tables (biblio & biblioitems)
@@ -367,10 +474,19 @@ Checks to make sure that the biblio has no items attached.
 return:
 C<$error> : undef unless an error occurs
 
 return:
 C<$error> : undef unless an error occurs
 
+I<$params> is a hashref containing extra parameters. Valid keys are:
+
+=over 4
+
+=item B<skip_holds_queue>: used when the holds queue update will be handled by the caller
+
+=item B<skip_record_index>: used when the indexing schedulling will be handled by the caller
+
+=back
 =cut
 
 sub DelBiblio {
 =cut
 
 sub DelBiblio {
-    my ($biblionumber) = @_;
+    my ($biblionumber, $params) = @_;
 
     my $biblio = Koha::Biblios->find( $biblionumber );
     return unless $biblio; # Should we throw an exception instead?
 
     my $biblio = Koha::Biblios->find( $biblionumber );
     return unless $biblio; # Should we throw an exception instead?
@@ -389,24 +505,17 @@ sub DelBiblio {
 
     return $error if $error;
 
 
     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 ) {
     # We delete any existing holds
     my $holds = $biblio->holds;
     while ( my $hold = $holds->next ) {
-        $hold->cancel;
+        # no need to update the holds queue on each step, we'll do it at the end
+        $hold->cancel({ skip_holds_queue => 1 });
     }
 
     }
 
-    # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
-    # for at least 2 reasons :
-    # - if something goes wrong, the biblio may be deleted from Koha but not from zebra
-    #   and we would have no way to remove it (except manually in zebra, but I bet it would be very hard to handle the problem)
-    ModZebra( $biblionumber, "recordDelete", "biblioserver" );
+    unless ( $params->{skip_record_index} ){
+        my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+        $indexer->index_records( $biblionumber, "recordDelete", "biblioserver" );
+    }
 
     # delete biblioitems and items from Koha tables and save in deletedbiblioitems,deleteditems
     $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
 
     # delete biblioitems and items from Koha tables and save in deletedbiblioitems,deleteditems
     $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
@@ -429,6 +538,12 @@ sub DelBiblio {
 
     logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
 
 
     logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
 
+    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+        {
+            biblio_ids => [ $biblionumber ]
+        }
+    ) unless $params->{skip_holds_queue} or !C4::Context->preference('RealTimeHoldsQueue');
+
     return;
 }
 
     return;
 }
 
@@ -446,6 +561,7 @@ Returns the number of headings changed
 sub BiblioAutoLink {
     my $record        = shift;
     my $frameworkcode = shift;
 sub BiblioAutoLink {
     my $record        = shift;
     my $frameworkcode = shift;
+    my $verbose = shift;
     if (!$record) {
         carp('Undefined record passed to BiblioAutoLink');
         return 0;
     if (!$record) {
         carp('Undefined record passed to BiblioAutoLink');
         return 0;
@@ -463,15 +579,15 @@ sub BiblioAutoLink {
 
     my $linker = $linker_module->new(
         { 'options' => C4::Context->preference("LinkerOptions") } );
 
     my $linker = $linker_module->new(
         { 'options' => C4::Context->preference("LinkerOptions") } );
-    my ( $headings_changed, undef ) =
-      LinkBibHeadingsToAuthorities( $linker, $record, $frameworkcode, C4::Context->preference("CatalogModuleRelink") || '' );
+    my ( $headings_changed, $results ) =
+      LinkBibHeadingsToAuthorities( $linker, $record, $frameworkcode, C4::Context->preference("CatalogModuleRelink") || '', undef, $verbose );
     # By default we probably don't want to relink things when cataloging
     # By default we probably don't want to relink things when cataloging
-    return $headings_changed;
+    return $headings_changed, $results;
 }
 
 =head2 LinkBibHeadingsToAuthorities
 
 }
 
 =head2 LinkBibHeadingsToAuthorities
 
-  my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink]);
+  my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink, $tagtolink,  $verbose]);
 
 Links bib headings to authority records by checking
 each authority-controlled field in the C<MARC::Record>
 
 Links bib headings to authority records by checking
 each authority-controlled field in the C<MARC::Record>
@@ -493,6 +609,8 @@ sub LinkBibHeadingsToAuthorities {
     my $bib           = shift;
     my $frameworkcode = shift;
     my $allowrelink = shift;
     my $bib           = shift;
     my $frameworkcode = shift;
     my $allowrelink = shift;
+    my $tagtolink     = shift;
+    my $verbose = shift;
     my %results;
     if (!$bib) {
         carp 'LinkBibHeadingsToAuthorities called on undefined bib record';
     my %results;
     if (!$bib) {
         carp 'LinkBibHeadingsToAuthorities called on undefined bib record';
@@ -504,7 +622,10 @@ sub LinkBibHeadingsToAuthorities {
     $allowrelink = 1 unless defined $allowrelink;
     my $num_headings_changed = 0;
     foreach my $field ( $bib->fields() ) {
     $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 );
+        if ( defined $tagtolink ) {
+          next unless $field->tag() == $tagtolink ;
+        }
+        my $heading = C4::Heading->new_from_field( $field, $frameworkcode );
         next unless defined $heading;
 
         # check existing $9
         next unless defined $heading;
 
         # check existing $9
@@ -513,30 +634,37 @@ sub LinkBibHeadingsToAuthorities {
         if ( defined $current_link && (!$allowrelink || !C4::Context->preference('LinkerRelink')) )
         {
             $results{'linked'}->{ $heading->display_form() }++;
         if ( defined $current_link && (!$allowrelink || !C4::Context->preference('LinkerRelink')) )
         {
             $results{'linked'}->{ $heading->display_form() }++;
+            push(@{$results{'details'}}, { tag => $field->tag(), authid => $current_link, status => 'UNCHANGED'}) if $verbose;
             next;
         }
 
             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() }++;
         if ($authid) {
             $results{ $fuzzy ? 'fuzzy' : 'linked' }
               ->{ $heading->display_form() }++;
-            next if defined $current_link and $current_link == $authid;
+            if(defined $current_link and $current_link == $authid) {
+                push(@{$results{'details'}}, { tag => $field->tag(), authid => $current_link, status => 'UNCHANGED'}) if $verbose;
+                next;
+            }
 
             $field->delete_subfield( code => '9' ) if defined $current_link;
             $field->add_subfields( '9', $authid );
             $num_headings_changed++;
 
             $field->delete_subfield( code => '9' ) if defined $current_link;
             $field->add_subfields( '9', $authid );
             $num_headings_changed++;
+            push(@{$results{'details'}}, { tag => $field->tag(), authid => $authid, status => 'LOCAL_FOUND'}) if $verbose;
         }
         else {
         }
         else {
+            my $authority_type = Koha::Authority::Types->find( $heading->auth_type() );
             if ( defined $current_link
                 && (!$allowrelink || C4::Context->preference('LinkerKeepStale')) )
             {
                 $results{'fuzzy'}->{ $heading->display_form() }++;
             if ( defined $current_link
                 && (!$allowrelink || C4::Context->preference('LinkerKeepStale')) )
             {
                 $results{'fuzzy'}->{ $heading->display_form() }++;
+                push(@{$results{'details'}}, { tag => $field->tag(), authid => $current_link, status => 'UNCHANGED'}) if $verbose;
             }
             elsif ( C4::Context->preference('AutoCreateAuthorities') ) {
                 if ( _check_valid_auth_link( $current_link, $field ) ) {
                     $results{'linked'}->{ $heading->display_form() }++;
                 }
             }
             elsif ( C4::Context->preference('AutoCreateAuthorities') ) {
                 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' ) {
                     my $authority_type = Koha::Authority::Types->find( $heading->auth_type() );
                     my $marcrecordauth = MARC::Record->new();
                     if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
@@ -545,16 +673,22 @@ sub LinkBibHeadingsToAuthorities {
                     }
                     $field->delete_subfield( code => '9' )
                       if defined $current_link;
                     }
                     $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
                     $marcrecordauth->insert_fields_ordered($authfield);
 
 # bug 2317: ensure new authority knows it's using UTF-8; currently
@@ -573,7 +707,7 @@ sub LinkBibHeadingsToAuthorities {
                         $marcrecordauth->insert_fields_ordered(
                             MARC::Field->new(
                                 '667', '', '',
                         $marcrecordauth->insert_fields_ordered(
                             MARC::Field->new(
                                 '667', '', '',
-                                'a' => "Machine generated authority record."
+                                'a' => C4::Context->preference('GenerateAuthorityField667')
                             )
                         );
                         my $cite =
                             )
                         );
                         my $cite =
@@ -583,7 +717,7 @@ sub LinkBibHeadingsToAuthorities {
                         $cite =~ s/^[\s\,]*//;
                         $cite =~ s/[\s\,]*$//;
                         $cite =
                         $cite =~ s/^[\s\,]*//;
                         $cite =~ s/[\s\,]*$//;
                         $cite =
-                            "Work cat.: ("
+                            C4::Context->preference('GenerateAuthorityField670') . ": ("
                           . ( $library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') ) . ")"
                           . $bib->subfield( '999', 'c' ) . ": "
                           . $cite;
                           . ( $library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') ) . ")"
                           . $bib->subfield( '999', 'c' ) . ": "
                           . $cite;
@@ -600,24 +734,29 @@ sub LinkBibHeadingsToAuthorities {
                     $num_headings_changed++;
                     $linker->update_cache($heading, $authid);
                     $results{'added'}->{ $heading->display_form() }++;
                     $num_headings_changed++;
                     $linker->update_cache($heading, $authid);
                     $results{'added'}->{ $heading->display_form() }++;
+                    push(@{$results{'details'}}, { tag => $field->tag(), authid => $authid, status => 'CREATED'}) if $verbose;
                 }
             }
             elsif ( defined $current_link ) {
                 if ( _check_valid_auth_link( $current_link, $field ) ) {
                     $results{'linked'}->{ $heading->display_form() }++;
                 }
             }
             elsif ( defined $current_link ) {
                 if ( _check_valid_auth_link( $current_link, $field ) ) {
                     $results{'linked'}->{ $heading->display_form() }++;
+                    push(@{$results{'details'}}, { tag => $field->tag(), authid => $authid, status => 'UNCHANGED'}) if $verbose;
                 }
                 else {
                     $field->delete_subfield( code => '9' );
                     $num_headings_changed++;
                     $results{'unlinked'}->{ $heading->display_form() }++;
                 }
                 else {
                     $field->delete_subfield( code => '9' );
                     $num_headings_changed++;
                     $results{'unlinked'}->{ $heading->display_form() }++;
+                    push(@{$results{'details'}}, { tag => $field->tag(), authid => undef, status => 'NONE_FOUND', auth_type => $heading->auth_type(), tag_to_report => $authority_type->auth_tag_to_report}) if $verbose;
                 }
             }
             else {
                 $results{'unlinked'}->{ $heading->display_form() }++;
                 }
             }
             else {
                 $results{'unlinked'}->{ $heading->display_form() }++;
+                push(@{$results{'details'}}, { tag => $field->tag(), authid => undef, status => 'NONE_FOUND', auth_type => $heading->auth_type(), tag_to_report => $authority_type->auth_tag_to_report}) if $verbose;
             }
         }
 
     }
             }
         }
 
     }
+    push(@{$results{'details'}}, { tag => '', authid => undef, status => 'UNCHANGED'}) unless %results;
     return $num_headings_changed, \%results;
 }
 
     return $num_headings_changed, \%results;
 }
 
@@ -638,41 +777,7 @@ sub _check_valid_auth_link {
     my ( $authid, $field ) = @_;
     require C4::AuthoritiesMarc;
 
     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);
-
-Get MARC fields from the record using the framework mappings for biblio fields.
-
-=cut
-
-sub GetRecordValue {
-    my ( $field, $record ) = @_;
-
-    if (!$record) {
-        carp 'GetRecordValue called with undefined record';
-        return;
-    }
-
-    my @result;
-    my @mss = GetMarcSubfieldStructureFromKohaField("biblio.$field");
-    foreach my $fldhash ( @mss ) {
-        my $tag = $fldhash->{tagfield};
-        my $sub = $fldhash->{tagsubfield};
-        foreach my $fld ( $record->field($tag) ) {
-            if( $sub eq '@' || $fld->is_control_field ) {
-                push @result, $fld->data if $fld->data;
-            } else {
-                push @result, grep { $_ } $fld->subfield($sub);
-            }
-        }
-    }
-    return \@result;
+    return C4::AuthoritiesMarc::CompareFieldWithAuthority( { 'field' => $field, 'authid' => $authid } );
 }
 
 =head2 GetBiblioData
 }
 
 =head2 GetBiblioData
@@ -859,7 +964,7 @@ sub GetISBDView {
         # Process subfield
     }
 
         # 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
 These different values should not be processed as valid subfields.
 
 =cut
@@ -897,66 +1002,31 @@ 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,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);
         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, $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}->{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;
     }
 
         $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
-         FROM   marc_subfield_structure 
-         WHERE  frameworkcode=? 
-         ORDER BY tagfield,tagsubfield
-        "
-    );
-
-    $sth->execute($frameworkcode);
-
-    my $subfield;
-    my $authorised_value;
-    my $authtypecode;
-    my $value_builder;
-    my $kohafield;
-    my $seealso;
-    my $hidden;
-    my $isurl;
-    my $link;
-    my $defaultvalue;
-    my $maxlength;
-
-    while (
-        (   $tag,          $subfield,      $liblibrarian, $libopac, $tab,    $mandatory, $repeatable, $authorised_value,
-            $authtypecode, $value_builder, $kohafield,    $seealso, $hidden, $isurl,     $link,       $defaultvalue,
-            $maxlength
-        )
-        = $sth->fetchrow
-      ) {
-        $res->{$tag}->{$subfield}->{lib}              = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
-        $res->{$tag}->{$subfield}->{tab}              = $tab;
-        $res->{$tag}->{$subfield}->{mandatory}        = $mandatory;
-        $res->{$tag}->{$subfield}->{repeatable}       = $repeatable;
-        $res->{$tag}->{$subfield}->{authorised_value} = $authorised_value;
-        $res->{$tag}->{$subfield}->{authtypecode}     = $authtypecode;
-        $res->{$tag}->{$subfield}->{value_builder}    = $value_builder;
-        $res->{$tag}->{$subfield}->{kohafield}        = $kohafield;
-        $res->{$tag}->{$subfield}->{seealso}          = $seealso;
-        $res->{$tag}->{$subfield}->{hidden}           = $hidden;
-        $res->{$tag}->{$subfield}->{isurl}            = $isurl;
-        $res->{$tag}->{$subfield}->{'link'}           = $link;
-        $res->{$tag}->{$subfield}->{defaultvalue}     = $defaultvalue;
-        $res->{$tag}->{$subfield}->{maxlength}        = $maxlength;
+    my $mss = Koha::MarcSubfieldStructures->search( { frameworkcode => $frameworkcode } )->unblessed;
+    for my $m (@$mss) {
+        $res->{ $m->{tagfield} }->{ $m->{tagsubfield} } = {
+            lib => ( $forlibrarian or !$m->{libopac} ) ? $m->{liblibrarian} : $m->{libopac},
+            subfield => $m->{tagsubfield},
+            %$m
+        };
     }
 
     $cache->set_in_cache($cache_key, $res);
     }
 
     $cache->set_in_cache($cache_key, $res);
@@ -984,7 +1054,7 @@ sub GetUsedMarcStructure {
         FROM   marc_subfield_structure
         WHERE   tab > -1 
             AND frameworkcode = ?
         FROM   marc_subfield_structure
         WHERE   tab > -1 
             AND frameworkcode = ?
-        ORDER BY tagfield, tagsubfield
+        ORDER BY tagfield, display_order, tagsubfield
     };
     my $sth = C4::Context->dbh->prepare($query);
     $sth->execute($frameworkcode);
     };
     my $sth = C4::Context->dbh->prepare($query);
     $sth->execute($frameworkcode);
@@ -1049,7 +1119,7 @@ sub GetMarcSubfieldStructure {
         FROM marc_subfield_structure
         WHERE frameworkcode = ?
         AND kohafield > ''
         FROM marc_subfield_structure
         WHERE frameworkcode = ?
         AND kohafield > ''
-        ORDER BY frameworkcode,tagfield,tagsubfield
+        ORDER BY frameworkcode, tagfield, display_order, tagsubfield
     |, { Slice => {} }, $frameworkcode );
     # Now map the output to a hash structure
     my $subfield_structure = {};
     |, { Slice => {} }, $frameworkcode );
     # Now map the output to a hash structure
     my $subfield_structure = {};
@@ -1114,99 +1184,6 @@ sub GetMarcSubfieldStructureFromKohaField {
     return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
 }
 
     return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
 }
 
-=head2 GetMarcBiblio
-
-  my $record = GetMarcBiblio({
-      biblionumber => $biblionumber,
-      embed_items  => $embeditems,
-      opac         => $opac,
-      borcat       => $patron_category });
-
-Returns MARC::Record representing a biblio record, or C<undef> if the
-biblionumber doesn't exist.
-
-Both embed_items and opac are optional.
-If embed_items is passed and is 1, items are embedded.
-If opac is passed and is 1, the record is filtered as needed.
-
-=over 4
-
-=item C<$biblionumber>
-
-the biblionumber
-
-=item C<$embeditems>
-
-set to true to include item information.
-
-=item C<$opac>
-
-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
-
-sub GetMarcBiblio {
-    my ($params) = @_;
-
-    if (not defined $params) {
-        carp 'GetMarcBiblio called without parameters';
-        return;
-    }
-
-    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';
-        return;
-    }
-
-    my $dbh          = C4::Context->dbh;
-    my $sth          = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=? ");
-    $sth->execute($biblionumber);
-    my $row     = $sth->fetchrow_hashref;
-    my $biblioitemnumber = $row->{'biblioitemnumber'};
-    my $marcxml = GetXmlBiblio( $biblionumber );
-    $marcxml = StripNonXmlChars( $marcxml );
-    my $frameworkcode = GetFrameworkCode($biblionumber);
-    MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
-    my $record = MARC::Record->new();
-
-    if ($marcxml) {
-        $record = eval {
-            MARC::Record::new_from_xml( $marcxml, "utf8",
-                C4::Context->preference('marcflavour') );
-        };
-        if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
-        return unless $record;
-
-        C4::Biblio::_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber,
-            $biblioitemnumber );
-        C4::Biblio::EmbedItemsInMarcBiblio({
-            marc_record  => $record,
-            biblionumber => $biblionumber,
-            opac         => $opac,
-            borcat       => $borcat })
-          if ($embeditems);
-
-        return $record;
-    }
-    else {
-        return;
-    }
-}
-
 =head2 GetXmlBiblio
 
   my $marcxml = GetXmlBiblio($biblionumber);
 =head2 GetXmlBiblio
 
   my $marcxml = GetXmlBiblio($biblionumber);
@@ -1252,7 +1229,7 @@ sub GetMarcPrice {
     my @listtags;
     my $subfield;
     
     my @listtags;
     my $subfield;
     
-    if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
+    if ( $marcflavour eq "MARC21" ) {
         @listtags = ('345', '020');
         $subfield="c";
     } elsif ( $marcflavour eq "UNIMARC" ) {
         @listtags = ('345', '020');
         $subfield="c";
     } elsif ( $marcflavour eq "UNIMARC" ) {
@@ -1413,6 +1390,11 @@ sub GetAuthorisedValueDesc {
             return $itemtype ? $itemtype->translated_description : q||;
         }
 
             return $itemtype ? $itemtype->translated_description : q||;
         }
 
+        if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "cn_source" ) {
+            my $source = GetClassSource($value);
+            return $source ? $source->{description} : q||;
+        }
+
         #---- "true" authorized value
         $category = $tagslib->{$tag}->{$subfield}->{'authorised_value'};
     }
         #---- "true" authorized value
         $category = $tagslib->{$tag}->{$subfield}->{'authorised_value'};
     }
@@ -1443,9 +1425,9 @@ sub GetMarcControlnumber {
         return;
     }
     my $controlnumber = "";
         return;
     }
     my $controlnumber = "";
-    # Control number or Record identifier are the same field in MARC21, UNIMARC and NORMARC
+    # Control number or Record identifier are the same field in MARC21 and UNIMARC
     # Keep $marcflavour for possible later use
     # Keep $marcflavour for possible later use
-    if ($marcflavour eq "MARC21" || $marcflavour eq "UNIMARC" || $marcflavour eq "NORMARC") {
+    if ($marcflavour eq "MARC21" || $marcflavour eq "UNIMARC" ) {
         my $controlnumberField = $record->field('001');
         if ($controlnumberField) {
             $controlnumber = $controlnumberField->data();
         my $controlnumberField = $record->field('001');
         if ($controlnumberField) {
             $controlnumber = $controlnumberField->data();
@@ -1507,7 +1489,7 @@ sub GetMarcISSN {
     if ( $marcflavour eq "UNIMARC" ) {
         $scope = '011';
     }
     if ( $marcflavour eq "UNIMARC" ) {
         $scope = '011';
     }
-    else {    # assume MARC21 or NORMARC
+    else {    # assume MARC21
         $scope = '022';
     }
     my @marcissns;
         $scope = '022';
     }
     my @marcissns;
@@ -1518,49 +1500,6 @@ sub GetMarcISSN {
     return \@marcissns;
 }    # end GetMarcISSN
 
     return \@marcissns;
 }    # end GetMarcISSN
 
-=head2 GetMarcNotes
-
-    $marcnotesarray = GetMarcNotes( $record, $marcflavour );
-
-    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 5XX $u subfields receive special attention as they are URIs.
-
-=cut
-
-sub GetMarcNotes {
-    my ( $record, $marcflavour ) = @_;
-    if (!$record) {
-        carp 'GetMarcNotes called on undefined record';
-        return;
-    }
-
-    my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
-    my @marcnotes;
-    my %blacklist = map { $_ => 1 }
-        split( /,/, C4::Context->preference('NotesBlacklist'));
-    foreach my $field ( $record->field($scope) ) {
-        my $tag = $field->tag();
-        next if $blacklist{ $tag };
-        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') ) {
-                $sub =~ s/^\s+|\s+$//g; # trim
-                push @marcnotes, { marcnote => $sub };
-            }
-        } else {
-            push @marcnotes, { marcnote => $field->as_string() };
-        }
-    }
-    return \@marcnotes;
-}
-
 =head2 GetMarcSubjects
 
   $marcsubjcts = GetMarcSubjects($record,$marcflavour);
 =head2 GetMarcSubjects
 
   $marcsubjcts = GetMarcSubjects($record,$marcflavour);
@@ -1581,7 +1520,7 @@ sub GetMarcSubjects {
         $mintag = "600";
         $maxtag = "611";
         $fields_filter = '6..';
         $mintag = "600";
         $maxtag = "611";
         $fields_filter = '6..';
-    } else { # marc21/normarc
+    } else { # marc21
         $mintag = "600";
         $maxtag = "699";
         $fields_filter = '6..';
         $mintag = "600";
         $maxtag = "699";
         $fields_filter = '6..';
@@ -1626,7 +1565,7 @@ sub GetMarcSubjects {
                 push @link_loop, {
                     limit    => $subject_limit,
                     'link'   => $linkvalue,
                 push @link_loop, {
                     limit    => $subject_limit,
                     'link'   => $linkvalue,
-                    operator => (scalar @link_loop) ? ' and ' : undef
+                    operator => (scalar @link_loop) ? ' AND ' : undef
                 };
             }
             my @this_link_loop = @link_loop;
                 };
             }
             my @this_link_loop = @link_loop;
@@ -1650,105 +1589,6 @@ sub GetMarcSubjects {
     return \@marcsubjects;
 }    #end getMARCsubjects
 
     return \@marcsubjects;
 }    #end getMARCsubjects
 
-=head2 GetMarcAuthors
-
-  authors = GetMarcAuthors($record,$marcflavour);
-
-Get all authors from the MARC record and returns them in an array.
-The authors are stored in different fields depending on MARC flavour
-
-=cut
-
-sub GetMarcAuthors {
-    my ( $record, $marcflavour ) = @_;
-    if (!$record) {
-        carp 'GetMarcAuthors called on undefined record';
-        return;
-    }
-    my ( $mintag, $maxtag, $fields_filter );
-
-    # tagslib useful only for UNIMARC author responsibilities
-    my $tagslib;
-    if ( $marcflavour eq "UNIMARC" ) {
-        # FIXME : we don't have the framework available, we take the default framework. May be buggy on some setups, will be usually correct.
-        $tagslib = GetMarcStructure( 1, '', { unsafe => 1 });
-        $mintag = "700";
-        $maxtag = "712";
-        $fields_filter = '7..';
-    } else { # marc21/normarc
-        $mintag = "700";
-        $maxtag = "720";
-        $fields_filter = '7..';
-    }
-
-    my @marcauthors;
-    my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
-
-    foreach my $field ( $record->field($fields_filter) ) {
-        next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
-        my @subfields_loop;
-        my @link_loop;
-        my @subfields  = $field->subfields();
-        my $count_auth = 0;
-
-        # if there is an authority link, build the link with Koha-Auth-Number: subfield9
-        my $subfield9 = $field->subfield('9');
-        if ($subfield9) {
-            my $linkvalue = $subfield9;
-            $linkvalue =~ s/(\(|\))//g;
-            @link_loop = ( { 'limit' => 'an', 'link' => $linkvalue } );
-        }
-
-        # other subfields
-        my $unimarc3;
-        for my $authors_subfield (@subfields) {
-            next if ( $authors_subfield->[0] eq '9' );
-
-            # unimarc3 contains the $3 of the author for UNIMARC.
-            # For french academic libraries, it's the "ppn", and it's required for idref webservice
-            $unimarc3 = $authors_subfield->[1] if $marcflavour eq 'UNIMARC' and $authors_subfield->[0] =~ /3/;
-
-            # don't load unimarc subfields 3, 5
-            next if ( $marcflavour eq 'UNIMARC' and ( $authors_subfield->[0] =~ /3|5/ ) );
-
-            my $code = $authors_subfield->[0];
-            my $value        = $authors_subfield->[1];
-            my $linkvalue    = $value;
-            $linkvalue =~ s/(\(|\))//g;
-            # UNIMARC author responsibility
-            if ( $marcflavour eq 'UNIMARC' and $code eq '4' ) {
-                $value = GetAuthorisedValueDesc( $field->tag(), $code, $value, '', $tagslib );
-                $linkvalue = "($value)";
-            }
-            # if no authority link, build a search query
-            unless ($subfield9) {
-                push @link_loop, {
-                    limit    => 'au',
-                    'link'   => $linkvalue,
-                    operator => (scalar @link_loop) ? ' and ' : undef
-                };
-            }
-            my @this_link_loop = @link_loop;
-            # do not display $0
-            unless ( $code eq '0') {
-                push @subfields_loop, {
-                    tag       => $field->tag(),
-                    code      => $code,
-                    value     => $value,
-                    link_loop => \@this_link_loop,
-                    separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
-                };
-            }
-        }
-        push @marcauthors, {
-            MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop,
-            authoritylink => $subfield9,
-            unimarc3 => $unimarc3
-        };
-    }
-    return \@marcauthors;
-}
-
 =head2 GetMarcUrls
 
   $marcurls = GetMarcUrls($record,$marcflavour);
 =head2 GetMarcUrls
 
   $marcurls = GetMarcUrls($record,$marcflavour);
@@ -1830,7 +1670,7 @@ sub GetMarcSeries {
         $mintag = "225";
         $maxtag = "225";
         $fields_filter = '2..';
         $mintag = "225";
         $maxtag = "225";
         $fields_filter = '2..';
-    } else {    # marc21/normarc
+    } else {    # marc21
         $mintag = "440";
         $maxtag = "490";
         $fields_filter = '4..';
         $mintag = "440";
         $maxtag = "490";
         $fields_filter = '4..';
@@ -1863,7 +1703,7 @@ sub GetMarcSeries {
 
             push @link_loop, {
                 'link' => $linkvalue,
 
             push @link_loop, {
                 'link' => $linkvalue,
-                operator => (scalar @link_loop) ? ' and ' : undef
+                operator => (scalar @link_loop) ? ' AND ' : undef
             };
 
             if ($volume_number) {
             };
 
             if ($volume_number) {
@@ -1884,53 +1724,6 @@ sub GetMarcSeries {
     return \@marcseries;
 }    #end getMARCseriess
 
     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);
 =head2 UpsertMarcSubfield
 
     my $record = C4::Biblio::UpsertMarcSubfield($MARC::Record, $fieldTag, $subfieldCode, $subfieldContent);
@@ -2006,16 +1799,18 @@ sub TransformKohaToMarc {
 
     # In the next call we use the Default framework, since it is considered
     # authoritative for Koha to Marc mappings.
 
     # 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 $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} //= [];
             foreach my $value ( @values ) {
                 next if $value eq '';
                 $tag_hr->{$tagfield} //= [];
@@ -2036,6 +1831,25 @@ sub TransformKohaToMarc {
     return $record;
 }
 
     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 )
 =head2 PrepHostMarcField
 
     $hostfield = PrepHostMarcField ( $hostbiblionumber,$hostitemnumber,$marcflavour )
@@ -2047,12 +1861,13 @@ This function returns a host field populated with data from the host record, the
 sub PrepHostMarcField {
     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
     $marcflavour ||="MARC21";
 sub PrepHostMarcField {
     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
     $marcflavour ||="MARC21";
-    
-    my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
+
+    my $biblio = Koha::Biblios->find($hostbiblionumber);
+    my $hostrecord = $biblio->metadata->record;
     my $item = Koha::Items->find($hostitemnumber);
 
        my $hostmarcfield;
     my $item = Koha::Items->find($hostitemnumber);
 
        my $hostmarcfield;
-    if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
+    if ( $marcflavour eq "MARC21" ) {
        
         #main entry
         my $mainentry;
        
         #main entry
         my $mainentry;
@@ -2138,9 +1953,11 @@ sub TransformHtmlToXml {
     my ( $tags, $subfields, $values, $indicator, $ind_tag, $auth_type ) = @_;
     # NOTE: The parameter $ind_tag is NOT USED -- BZ 11247
 
     my ( $tags, $subfields, $values, $indicator, $ind_tag, $auth_type ) = @_;
     # NOTE: The parameter $ind_tag is NOT USED -- BZ 11247
 
+    my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
+
     my $xml = MARC::File::XML::header('UTF-8');
     $xml .= "<record>\n";
     my $xml = MARC::File::XML::header('UTF-8');
     $xml .= "<record>\n";
-    $auth_type = C4::Context->preference('marcflavour') unless $auth_type;
+    $auth_type = C4::Context->preference('marcflavour') unless $auth_type; # FIXME auth_type must be removed
     MARC::File::XML->default_record_format($auth_type);
 
     # in UNIMARC, field 100 contains the encoding
     MARC::File::XML->default_record_format($auth_type);
 
     # in UNIMARC, field 100 contains the encoding
@@ -2148,13 +1965,11 @@ 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
     # 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;
     my $close_last_tag;
     for ( my $i = 0 ; $i < @$tags ; $i++ ) {
     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" ) {
 
             # if we have a 100 field and it's values are not correct, skip them.
         if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and @$tags[$i] eq "100" and @$subfields[$i] eq "a" ) {
 
             # if we have a 100 field and it's values are not correct, skip them.
@@ -2172,6 +1987,13 @@ sub TransformHtmlToXml {
         @$values[$i] =~ s/"/&quot;/g;
         @$values[$i] =~ s/'/&apos;/g;
 
         @$values[$i] =~ s/"/&quot;/g;
         @$values[$i] =~ s/'/&apos;/g;
 
+        my $skip = @$values[$i] eq q{};
+        $skip = 0
+          if $perm_loc_tag
+          && $perm_loc_subfield
+          && @$tags[$i] eq $perm_loc_tag
+          && @$subfields[$i] eq $perm_loc_subfield;
+
         if ( ( @$tags[$i] ne $prevtag ) ) {
             $close_last_tag = 0;
             $j++ unless ( @$tags[$i] eq "" );
         if ( ( @$tags[$i] ne $prevtag ) ) {
             $close_last_tag = 0;
             $j++ unless ( @$tags[$i] eq "" );
@@ -2181,7 +2003,7 @@ sub TransformHtmlToXml {
             if ( !$first ) {
                 $xml .= "</datafield>\n";
                 if (   ( @$tags[$i] && @$tags[$i] > 10 )
             if ( !$first ) {
                 $xml .= "</datafield>\n";
                 if (   ( @$tags[$i] && @$tags[$i] > 10 )
-                    && ( @$values[$i] ne "" ) ) {
+                    && ( !$skip ) ) {
                     $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;
@@ -2190,7 +2012,7 @@ sub TransformHtmlToXml {
                     $first = 1;
                 }
             } else {
                     $first = 1;
                 }
             } else {
-                if ( @$values[$i] ne "" ) {
+                if ( !$skip ) {
 
                     # leader
                     if ( @$tags[$i] eq "000" ) {
 
                     # leader
                     if ( @$tags[$i] eq "000" ) {
@@ -2210,8 +2032,7 @@ sub TransformHtmlToXml {
                 }
             }
         } else {    # @$tags[$i] eq $prevtag
                 }
             }
         } else {    # @$tags[$i] eq $prevtag
-            if ( @$values[$i] eq "" ) {
-            } else {
+            if ( !$skip ) {
                 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 ) );
                 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 ) );
@@ -2375,13 +2196,14 @@ sub TransformHtmlToMarc {
         }
     }
 
         }
     }
 
+    @fields = sort { $a->tag() cmp $b->tag() } @fields;
     $record->append_fields(@fields);
     return $record;
 }
 
 =head2 TransformMarcToKoha
 
     $record->append_fields(@fields);
     return $record;
 }
 
 =head2 TransformMarcToKoha
 
-    $result = TransformMarcToKoha( $record, undef, $limit )
+    $result = TransformMarcToKoha({ record => $record, limit_table => $limit })
 
 Extract data from a MARC bib record into a hashref representing
 Koha biblio, biblioitems, and items fields.
 
 Extract data from a MARC bib record into a hashref representing
 Koha biblio, biblioitems, and items fields.
@@ -2392,9 +2214,11 @@ hash_ref.
 =cut
 
 sub TransformMarcToKoha {
 =cut
 
 sub TransformMarcToKoha {
-    my ( $record, $frameworkcode, $limit_table ) = @_;
-    # FIXME  Parameter $frameworkcode is obsolete and will be removed
-    $limit_table //= q{};
+    my ( $params ) = @_;
+
+    my $record = $params->{record};
+    my $limit_table = $params->{limit_table} // q{};
+    my $kohafields = $params->{kohafields};
 
     my $result = {};
     if (!defined $record) {
 
     my $result = {};
     if (!defined $record) {
@@ -2405,18 +2229,41 @@ sub TransformMarcToKoha {
     my %tables = ( biblio => 1, biblioitems => 1, items => 1 );
     if( $limit_table eq 'items' ) {
         %tables = ( items => 1 );
     my %tables = ( biblio => 1, biblioitems => 1, items => 1 );
     if( $limit_table eq 'items' ) {
         %tables = ( items => 1 );
+    } elsif ( $limit_table eq 'no_items' ){
+        %tables = ( biblio => 1, biblioitems => 1 );
     }
 
     # The next call acknowledges Default as the authoritative framework
     # for Koha to MARC mappings.
     my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
     }
 
     # The next call acknowledges Default as the authoritative framework
     # for Koha to MARC mappings.
     my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
-    foreach my $kohafield ( keys %{ $mss } ) {
+    @{$kohafields} = keys %{ $mss } unless $kohafields;
+    foreach my $kohafield ( @{$kohafields} ) {
         my ( $table, $column ) = split /[.]/, $kohafield, 2;
         next unless $tables{$table};
         my ( $table, $column ) = split /[.]/, $kohafield, 2;
         next unless $tables{$table};
-        my $val = TransformMarcToKohaOneField( $kohafield, $record );
-        next if !defined $val;
+        my ( $value, @values );
+        foreach my $fldhash ( @{$mss->{$kohafield}} ) {
+            my $tag = $fldhash->{tagfield};
+            my $sub = $fldhash->{tagsubfield};
+            foreach my $fld ( $record->field($tag) ) {
+                if( $sub eq '@' || $fld->is_control_field ) {
+                    push @values, $fld->data if $fld->data;
+                } else {
+                    push @values, grep { $_ } $fld->subfield($sub);
+                }
+            }
+        }
+        if ( @values ){
+            $value = join ' | ', uniq(@values);
+
+            # Additional polishing for individual kohafields
+            if( $kohafield =~ /copyrightdate|publicationyear/ ) {
+                $value = _adjust_pubyear( $value );
+            }
+        }
+
+        next if !defined $value;
         my $key = _disambiguate( $table, $column );
         my $key = _disambiguate( $table, $column );
-        $result->{$key} = $val;
+        $result->{$key} = $value;
     }
     return $result;
 }
     }
     return $result;
 }
@@ -2460,44 +2307,9 @@ sub _disambiguate {
 
 }
 
 
 }
 
-=head2 TransformMarcToKohaOneField
-
-    $val = TransformMarcToKohaOneField( 'biblio.title', $marc );
-
-    Note: The authoritative Default framework is used implicitly.
-
-=cut
-
-sub TransformMarcToKohaOneField {
-    my ( $kohafield, $marc ) = @_;
-
-    my ( @rv, $retval );
-    my @mss = GetMarcSubfieldStructureFromKohaField($kohafield);
-    foreach my $fldhash ( @mss ) {
-        my $tag = $fldhash->{tagfield};
-        my $sub = $fldhash->{tagsubfield};
-        foreach my $fld ( $marc->field($tag) ) {
-            if( $sub eq '@' || $fld->is_control_field ) {
-                push @rv, $fld->data if $fld->data;
-            } else {
-                push @rv, grep { $_ } $fld->subfield($sub);
-            }
-        }
-    }
-    return unless @rv;
-    $retval = join ' | ', uniq(@rv);
-
-    # Additional polishing for individual kohafields
-    if( $kohafield =~ /copyrightdate|publicationyear/ ) {
-        $retval = _adjust_pubyear( $retval );
-    }
-
-    return $retval;
-}
-
 =head2 _adjust_pubyear
 
 =head2 _adjust_pubyear
 
-    Helper routine for TransformMarcToKohaOneField
+    Helper routine for TransformMarcToKoha
 
 =cut
 
 
 =cut
 
@@ -2508,16 +2320,13 @@ sub _adjust_pubyear {
         $retval = $1;
     } elsif( $retval =~ m/(\d\d\d\d)/ && $1 > 0 ) {
         $retval = $1;
         $retval = $1;
     } elsif( $retval =~ m/(\d\d\d\d)/ && $1 > 0 ) {
         $retval = $1;
-    } elsif( $retval =~ m/
-             (?<year>\d)[-]?[.Xx?]{3}
-            |(?<year>\d{2})[.Xx?]{2}
-            |(?<year>\d{3})[.Xx?]
-            |(?<year>\d)[-]{3}\?
-            |(?<year>\d\d)[-]{2}\?
-            |(?<year>\d{3})[-]\?
-    /xms ) { # the form 198-? occurred in Dutch ISBD rules
-        my $digits = $+{year};
-        $retval = $digits * ( 10 ** ( 4 - length($digits) ));
+    } elsif( $retval =~ m/(?<year>\d{1,3})[.Xx?-]/ ) {
+        # See also bug 24674: enough to look at one unknown year char like .Xx-?
+        # At this point in code 1234? or 1234- already passed the earlier regex
+        # Things like 2-, 1xx, 1??? are now converted to a four positions-year.
+        $retval = $+{year} * ( 10 ** (4-length($+{year})) );
+    } else {
+        $retval = undef;
     }
     return $retval;
 }
     }
     return $retval;
 }
@@ -2539,51 +2348,19 @@ sub CountItemsIssued {
 
 =head2 ModZebra
 
 
 =head2 ModZebra
 
-  ModZebra( $biblionumber, $op, $server, $record );
-
-$biblionumber is the biblionumber we want to index
+    ModZebra( $record_number, $op, $server );
 
 
-$op is specialUpdate or recordDelete, and is used to know what we want to do
+$record_number is the authid or biblionumber we want to index
 
 
-$server is the server that we want to update
+$op is the operation: specialUpdate or recordDelete
 
 
-$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.
+$server is authorityserver or biblioserver
 
 =cut
 
 sub ModZebra {
 
 =cut
 
 sub ModZebra {
-###Accepts a $server variable thus we can use it for biblios authorities or other zebra dbs
-    my ( $biblionumber, $op, $server, $record ) = @_;
-    $debug && warn "ModZebra: update requested for: $biblionumber $op $server\n";
-    if ( C4::Context->preference('SearchEngine') eq 'Elasticsearch' ) {
-
-        # TODO abstract to a standard API that'll work for whatever
-        require Koha::SearchEngine::Elasticsearch::Indexer;
-        my $indexer = Koha::SearchEngine::Elasticsearch::Indexer->new(
-            {
-                index => $server eq 'biblioserver'
-                ? $Koha::SearchEngine::BIBLIOS_INDEX
-                : $Koha::SearchEngine::AUTHORITIES_INDEX
-            }
-        );
-        if ( $op eq 'specialUpdate' ) {
-            unless ($record) {
-                $record = GetMarcBiblio({
-                    biblionumber => $biblionumber,
-                    embed_items  => 1 });
-            }
-            my $records = [$record];
-            $indexer->update_index_background( [$biblionumber], [$record] );
-        }
-        elsif ( $op eq 'recordDelete' ) {
-            $indexer->delete_index_background( [$biblionumber] );
-        }
-        else {
-            croak "ModZebra called with unknown operation: $op";
-        }
-    }
-
+    my ( $record_number, $op, $server ) = @_;
+    Koha::Logger->get->debug("ModZebra: updates requested for: $record_number $op $server");
     my $dbh = C4::Context->dbh;
 
     # true ModZebra commented until indexdata fixes zebraDB crashes (it seems they occur on multiple updates
     my $dbh = C4::Context->dbh;
 
     # true ModZebra commented until indexdata fixes zebraDB crashes (it seems they occur on multiple updates
@@ -2596,96 +2373,16 @@ sub ModZebra {
         AND   operation = ?
         AND   done = 0";
     my $check_sth = $dbh->prepare_cached($check_sql);
         AND   operation = ?
         AND   done = 0";
     my $check_sth = $dbh->prepare_cached($check_sql);
-    $check_sth->execute( $server, $biblionumber, $op );
+    $check_sth->execute( $server, $record_number, $op );
     my ($count) = $check_sth->fetchrow_array;
     $check_sth->finish();
     if ( $count == 0 ) {
         my $sth = $dbh->prepare("INSERT INTO zebraqueue  (biblio_auth_number,server,operation) VALUES(?,?,?)");
     my ($count) = $check_sth->fetchrow_array;
     $check_sth->finish();
     if ( $count == 0 ) {
         my $sth = $dbh->prepare("INSERT INTO zebraqueue  (biblio_auth_number,server,operation) VALUES(?,?,?)");
-        $sth->execute( $biblionumber, $server, $op );
+        $sth->execute( $record_number, $server, $op );
         $sth->finish;
     }
 }
 
         $sth->finish;
     }
 }
 
-
-=head2 EmbedItemsInMarcBiblio
-
-    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
-per the bib's MARC framework.
-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 ($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;
-
-    my $frameworkcode = GetFrameworkCode($biblionumber);
-    _strip_item_fields($marc, $frameworkcode);
-
-    # ... and embed the current items
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
-    $sth->execute($biblionumber);
-    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 $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({
-            items  => \@items2pass,
-            borcat => $borcat })
-      : ();
-    # Convert to a hash for quick searching
-    my %hiddenitems = map { $_ => 1 } @hiddenitems;
-    foreach my $itemnumber ( map { $_->{itemnumber} } @items ) {
-        next if $hiddenitems{$itemnumber};
-        my $item_marc = C4::Items::GetMarcItem( $biblionumber, $itemnumber );
-        push @item_fields, $item_marc->field($itemtag);
-    }
-    $marc->append_fields(@item_fields);
-}
-
 =head1 INTERNAL FUNCTIONS
 
 =head2 _koha_marc_update_bib_ids
 =head1 INTERNAL FUNCTIONS
 
 =head2 _koha_marc_update_bib_ids
@@ -2716,6 +2413,13 @@ sub _koha_marc_update_bib_ids {
     } else {
         C4::Biblio::UpsertMarcSubfield($record, $biblioitem_tag, $biblioitem_subfield, $biblioitemnumber);
     }
     } else {
         C4::Biblio::UpsertMarcSubfield($record, $biblioitem_tag, $biblioitem_subfield, $biblioitemnumber);
     }
+
+    # update the control number (001) in MARC
+    if(C4::Context->preference('autoControlNumber') eq 'biblionumber'){
+        unless($record->field('001')){
+            $record->insert_fields_ordered(MARC::Field->new('001', $biblionumber));
+        }
+    }
 }
 
 =head2 _koha_marc_update_biblioitem_cn_sort
 }
 
 =head2 _koha_marc_update_biblioitem_cn_sort
@@ -2753,61 +2457,6 @@ sub _koha_marc_update_biblioitem_cn_sort {
     }
 }
 
     }
 }
 
-=head2 _koha_add_biblio
-
-  my ($biblionumber,$error) = _koha_add_biblio($dbh,$biblioitem);
-
-Internal function to add a biblio ($biblio is a hash with the values)
-
-=cut
-
-sub _koha_add_biblio {
-    my ( $dbh, $biblio, $frameworkcode ) = @_;
-
-    my $error;
-
-    # set the series flag
-    unless (defined $biblio->{'serial'}){
-       $biblio->{'serial'} = 0;
-       if ( $biblio->{'seriestitle'} ) { $biblio->{'serial'} = 1 }
-    }
-
-    my $query = "INSERT INTO biblio
-        SET frameworkcode = ?,
-            author = ?,
-            title = ?,
-            subtitle = ?,
-            medium = ?,
-            part_number = ?,
-            part_name = ?,
-            unititle =?,
-            notes = ?,
-            serial = ?,
-            seriestitle = ?,
-            copyrightdate = ?,
-            datecreated=NOW(),
-            abstract = ?
-        ";
-    my $sth = $dbh->prepare($query);
-    $sth->execute(
-        $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'};
-    if ( $dbh->errstr ) {
-        $error .= "ERROR in _koha_add_biblio $query" . $dbh->errstr;
-        warn $error;
-    }
-
-    $sth->finish();
-
-    #warn "LEAVING _koha_add_biblio: ".$biblionumber."\n";
-    return ( $biblionumber, $error );
-}
-
 =head2 _koha_modify_biblio
 
   my ($biblionumber,$error) == _koha_modify_biblio($dbh,$biblio,$frameworkcode);
 =head2 _koha_modify_biblio
 
   my ($biblionumber,$error) == _koha_modify_biblio($dbh,$biblio,$frameworkcode);
@@ -2918,72 +2567,6 @@ sub _koha_modify_biblioitem_nonmarc {
     return ( $biblioitem->{'biblioitemnumber'}, $error );
 }
 
     return ( $biblioitem->{'biblioitemnumber'}, $error );
 }
 
-=head2 _koha_add_biblioitem
-
-  my ($biblioitemnumber,$error) = _koha_add_biblioitem( $dbh, $biblioitem );
-
-Internal function to add a biblioitem
-
-=cut
-
-sub _koha_add_biblioitem {
-    my ( $dbh, $biblioitem ) = @_;
-    my $error;
-
-    my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
-    my $query = "INSERT INTO biblioitems SET
-        biblionumber    = ?,
-        volume          = ?,
-        number          = ?,
-        itemtype        = ?,
-        isbn            = ?,
-        issn            = ?,
-        publicationyear = ?,
-        publishercode   = ?,
-        volumedate      = ?,
-        volumedesc      = ?,
-        collectiontitle = ?,
-        collectionissn  = ?,
-        collectionvolume= ?,
-        editionstatement= ?,
-        editionresponsibility = ?,
-        illus           = ?,
-        pages           = ?,
-        notes           = ?,
-        size            = ?,
-        place           = ?,
-        lccn            = ?,
-        url             = ?,
-        cn_source       = ?,
-        cn_class        = ?,
-        cn_item         = ?,
-        cn_suffix       = ?,
-        cn_sort         = ?,
-        totalissues     = ?,
-        ean             = ?,
-        agerestriction  = ?
-        ";
-    my $sth = $dbh->prepare($query);
-    $sth->execute(
-        $biblioitem->{'biblionumber'},     $biblioitem->{'volume'},           $biblioitem->{'number'},                $biblioitem->{'itemtype'},
-        $biblioitem->{'isbn'},             $biblioitem->{'issn'},             $biblioitem->{'publicationyear'},       $biblioitem->{'publishercode'},
-        $biblioitem->{'volumedate'},       $biblioitem->{'volumedesc'},       $biblioitem->{'collectiontitle'},       $biblioitem->{'collectionissn'},
-        $biblioitem->{'collectionvolume'}, $biblioitem->{'editionstatement'}, $biblioitem->{'editionresponsibility'}, $biblioitem->{'illus'},
-        $biblioitem->{'pages'},            $biblioitem->{'bnotes'},           $biblioitem->{'size'},                  $biblioitem->{'place'},
-        $biblioitem->{'lccn'},             $biblioitem->{'url'},                   $biblioitem->{'biblioitems.cn_source'},
-        $biblioitem->{'cn_class'},         $biblioitem->{'cn_item'},          $biblioitem->{'cn_suffix'},             $cn_sort,
-        $biblioitem->{'totalissues'},      $biblioitem->{'ean'},              $biblioitem->{'agerestriction'}
-    );
-    my $bibitemnum = $dbh->{'mysql_insertid'};
-
-    if ( $dbh->errstr ) {
-        $error .= "ERROR in _koha_add_biblioitem $query" . $dbh->errstr;
-        warn $error;
-    }
-    $sth->finish();
-    return ( $bibitemnum, $error );
-}
-
 =head2 _koha_delete_biblio
 
   $error = _koha_delete_biblio($dbh,$biblionumber);
 =head2 _koha_delete_biblio
 
   $error = _koha_delete_biblio($dbh,$biblionumber);
@@ -3116,7 +2699,7 @@ sub _koha_delete_biblio_metadata {
 
 =head2 ModBiblioMarc
 
 
 =head2 ModBiblioMarc
 
-  &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
+  ModBiblioMarc($newrec,$biblionumber);
 
 Add MARC XML data for a biblio to koha
 
 
 Add MARC XML data for a biblio to koha
 
@@ -3127,22 +2710,18 @@ Function exported, but should NOT be used, unless you really know what you're do
 sub ModBiblioMarc {
     # pass the MARC::Record to this function, and it will create the records in
     # the marcxml field
 sub ModBiblioMarc {
     # pass the MARC::Record to this function, and it will create the records in
     # the marcxml field
-    my ( $record, $biblionumber, $frameworkcode ) = @_;
+    my ( $record, $biblionumber, $params ) = @_;
     if ( !$record ) {
         carp 'ModBiblioMarc passed an undefined record';
         return;
     }
 
     if ( !$record ) {
         carp 'ModBiblioMarc passed an undefined record';
         return;
     }
 
+    my $skip_record_index = $params->{skip_record_index} || 0;
+
     # Clone record as it gets modified
     $record = $record->clone();
     my $dbh    = C4::Context->dbh;
     my @fields = $record->fields();
     # Clone record as it gets modified
     $record = $record->clone();
     my $dbh    = C4::Context->dbh;
     my @fields = $record->fields();
-    if ( !$frameworkcode ) {
-        $frameworkcode = "";
-    }
-    my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
-    $sth->execute( $frameworkcode, $biblionumber );
-    $sth->finish;
     my $encoding = C4::Context->preference("marcflavour");
 
     # deal with UNIMARC field 100 (encoding) : create it if needed & set encoding to unicode
     my $encoding = C4::Context->preference("marcflavour");
 
     # deal with UNIMARC field 100 (encoding) : create it if needed & set encoding to unicode
@@ -3198,31 +2777,14 @@ sub ModBiblioMarc {
     $m_rs->metadata( $record->as_xml_record($encoding) );
     $m_rs->store;
 
     $m_rs->metadata( $record->as_xml_record($encoding) );
     $m_rs->store;
 
-    ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
+    unless ( $skip_record_index ) {
+        my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+        $indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" );
+    }
 
     return $biblionumber;
 }
 
 
     return $biblionumber;
 }
 
-=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);
-}
-
 =head2 prepare_host_field
 
 $marcfield = prepare_host_field( $hostbiblioitem, $marcflavour );
 =head2 prepare_host_field
 
 $marcfield = prepare_host_field( $hostbiblioitem, $marcflavour );
@@ -3233,13 +2795,15 @@ Generate the host item entry for an analytic child entry
 sub prepare_host_field {
     my ( $hostbiblio, $marcflavour ) = @_;
     $marcflavour ||= C4::Context->preference('marcflavour');
 sub prepare_host_field {
     my ( $hostbiblio, $marcflavour ) = @_;
     $marcflavour ||= C4::Context->preference('marcflavour');
-    my $host = GetMarcBiblio({ biblionumber => $hostbiblio });
+
+    my $biblio = Koha::Biblios->find($hostbiblio);
+    my $host = $biblio->metadata->record;
     # unfortunately as_string does not 'do the right thing'
     # if field returns undef
     my %sfd;
     my $field;
     my $host_field;
     # unfortunately as_string does not 'do the right thing'
     # if field returns undef
     my %sfd;
     my $field;
     my $host_field;
-    if ( $marcflavour eq 'MARC21' || $marcflavour eq 'NORMARC' ) {
+    if ( $marcflavour eq 'MARC21' ) {
         if ( $field = $host->field('100') || $host->field('110') || $host->field('11') ) {
             my $s = $field->as_string('ab');
             if ($s) {
         if ( $field = $host->field('100') || $host->field('110') || $host->field('11') ) {
             my $s = $field->as_string('ab');
             if ($s) {
@@ -3368,17 +2932,18 @@ Update the total issue count for a particular bib record.
 =cut
 
 sub UpdateTotalIssues {
 =cut
 
 sub UpdateTotalIssues {
-    my ($biblionumber, $increase, $value) = @_;
+    my ($biblionumber, $increase, $value, $skip_holds_queue) = @_;
     my $totalissues;
 
     my $totalissues;
 
-    my $record = GetMarcBiblio({ biblionumber => $biblionumber });
-    unless ($record) {
-        carp "UpdateTotalIssues could not get biblio record";
+    my $biblio = Koha::Biblios->find($biblionumber);
+    unless ($biblio) {
+        carp "UpdateTotalIssues could not get biblio";
         return;
     }
         return;
     }
-    my $biblio = Koha::Biblios->find( $biblionumber );
-    unless ($biblio) {
-        carp "UpdateTotalIssues could not get datas of biblio";
+
+    my $record = $biblio->metadata->record;
+    unless ($record) {
+        carp "UpdateTotalIssues could not get biblio record";
         return;
     }
     my $biblioitem = $biblio->biblioitem;
         return;
     }
     my $biblioitem = $biblio->biblioitem;
@@ -3402,7 +2967,7 @@ sub UpdateTotalIssues {
          $record->insert_grouped_field($field);
      }
 
          $record->insert_grouped_field($field);
      }
 
-     return ModBiblio($record, $biblionumber, $biblio->frameworkcode);
+     return ModBiblio($record, $biblionumber, $biblio->frameworkcode, { skip_holds_queue => $skip_holds_queue });
 }
 
 =head2 RemoveAllNsb
 }
 
 =head2 RemoveAllNsb
@@ -3453,24 +3018,66 @@ sub RemoveAllNsb {
     return $record;
 }
 
     return $record;
 }
 
-=head2 SplitSubtitle
+=head2 ApplyMarcOverlayRules
 
 
-    $subtitles = SplitSubtitle($subtitle);
+    my $record = ApplyMarcOverlayRules($params)
 
 
-Splits a subtitle field to an array of hashes like the one GetRecordValue returns
+Applies marc merge rules to a record.
 
 
-=cut
+C<$params> is expected to be a hashref with below keys defined.
 
 
-sub SplitSubtitle {
-    my $subtitle = shift;
+=over 4
 
 
-    my @subtitles = map( { 'subfield' => $_ }, split(/ \| /, $subtitle // '' ) );
+=item C<biblionumber>
+biblionumber of old record
 
 
-    return \@subtitles;
-}
+=item C<record>
+Incoming record that will be merged with old record
 
 
-1;
+=item C<overlay_context>
+hashref containing at least one context module and filter value on
+the form {module => filter, ...}.
+
+=back
+
+Returns:
+
+=over 4
+
+=item C<$record>
+
+Merged MARC record based with merge rules for C<context> applied. If no old
+record for C<biblionumber> can be found, C<record> is returned unchanged.
+Default action when no matching context is found to return C<record> unchanged.
+If no rules are found for a certain field tag the default is to overwrite with
+fields with this field tag from C<record>.
 
 
+=back
+
+=cut
+
+sub ApplyMarcOverlayRules {
+    my ($params) = @_;
+    my $biblionumber = $params->{biblionumber};
+    my $incoming_record = $params->{record};
+
+    if (!$biblionumber) {
+        carp 'ApplyMarcOverlayRules called on undefined biblionumber';
+        return;
+    }
+    if (!$incoming_record) {
+        carp 'ApplyMarcOverlayRules called on undefined record';
+        return;
+    }
+    my $biblio = Koha::Biblios->find($biblionumber);
+    my $old_record = $biblio->metadata->record;
+
+    # Skip overlay rules if called with no context
+    if ($old_record && defined $params->{overlay_context}) {
+        return Koha::MarcOverlayRules->merge_records($old_record, $incoming_record, $params->{overlay_context});
+    }
+    return $incoming_record;
+}
 
 =head2 _after_biblio_action_hooks
 
 
 =head2 _after_biblio_action_hooks
 
@@ -3484,28 +3091,19 @@ sub _after_biblio_action_hooks {
     my $biblio_id = $args->{biblio_id};
     my $action    = $args->{action};
 
     my $biblio_id = $args->{biblio_id};
     my $action    = $args->{action};
 
-    if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
-
-        my @plugins = Koha::Plugins->new->GetPlugins({
-            method => 'after_biblio_action',
-        });
-
-        if (@plugins) {
-
-            my $biblio = Koha::Biblios->find( $biblio_id );
-
-            foreach my $plugin ( @plugins ) {
-                try {
-                    $plugin->after_biblio_action({ action => $action, biblio => $biblio, biblio_id => $biblio_id });
-                }
-                catch {
-                    warn "$_";
-                };
-            }
+    my $biblio = Koha::Biblios->find( $biblio_id );
+    Koha::Plugins->call(
+        'after_biblio_action',
+        {
+            action    => $action,
+            biblio    => $biblio,
+            biblio_id => $biblio_id,
         }
         }
-    }
+    );
 }
 
 }
 
+1;
+
 __END__
 
 =head1 AUTHOR
 __END__
 
 =head1 AUTHOR