Bug 17969: (QA followup) Add POD
[koha.git] / C4 / Biblio.pm
index 61e4c3b..54a6731 100644 (file)
@@ -6,24 +6,23 @@ package C4::Biblio;
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
-use strict;
-use warnings;
+use Modern::Perl;
 use Carp;
 
-# use utf8;
+use Encode qw( decode is_utf8 );
 use MARC::Record;
 use MARC::File::USMARC;
 use MARC::File::XML;
@@ -31,17 +30,28 @@ use POSIX qw(strftime);
 use Module::Load::Conditional qw(can_load);
 
 use C4::Koha;
-use C4::Dates qw/format_date/;
 use C4::Log;    # logaction
+use C4::Budgets;
 use C4::ClassSource;
 use C4::Charset;
 use C4::Linker;
 use C4::OAI::Sets;
+use C4::Debug;
 
-use vars qw($VERSION @ISA @EXPORT);
+use Koha::Caches;
+use Koha::Authority::Types;
+use Koha::Acquisition::Currencies;
+use Koha::Biblio::Metadata;
+use Koha::Biblio::Metadatas;
+use Koha::Holds;
+use Koha::ItemTypes;
+use Koha::SearchEngine;
+use Koha::Libraries;
+
+use vars qw(@ISA @EXPORT);
+use vars qw($debug $cgi_debug);
 
 BEGIN {
-    $VERSION = 3.07.00.049;
 
     require Exporter;
     @ISA = qw( Exporter );
@@ -54,18 +64,13 @@ BEGIN {
 
     # to get something
     push @EXPORT, qw(
-      &GetBiblio
-      &GetBiblioData
-      &GetBiblioItemData
-      &GetBiblioItemInfosOf
-      &GetBiblioItemByBiblioNumber
-      &GetBiblioFromItemNumber
-      &GetBiblionumberFromItemnumber
+      GetBiblioData
+      GetMarcBiblio
+      GetBiblioItemData
+      GetBiblioItemInfosOf
+      GetBiblioItemByBiblioNumber
 
       &GetRecordValue
-      &GetFieldMapping
-      &SetFieldMapping
-      &DeleteFieldMapping
 
       &GetISBDView
 
@@ -74,7 +79,6 @@ BEGIN {
       &GetMarcISBN
       &GetMarcISSN
       &GetMarcSubjects
-      &GetMarcBiblio
       &GetMarcAuthors
       &GetMarcSeries
       &GetMarcHosts
@@ -88,6 +92,7 @@ BEGIN {
 
       &GetAuthorisedValueDesc
       &GetMarcStructure
+      &IsMarcStructureInternal
       &GetMarcFromKohaField
       &GetMarcSubfieldStructureFromKohaField
       &GetFrameworkCode
@@ -96,14 +101,11 @@ BEGIN {
 
       &CountItemsIssued
       &CountBiblioInOrders
-      &GetSubscriptionsId
-      &GetHolds
     );
 
     # To modify something
     push @EXPORT, qw(
       &ModBiblio
-      &ModBiblioframework
       &ModZebra
       &UpdateTotalIssues
       &RemoveAllNsb
@@ -123,8 +125,8 @@ BEGIN {
 
     # Internal functions
     # those functions are exported but should not be used
-    # they are usefull is few circumstances, so are exported.
-    # but don't use them unless you're a core developer ;-)
+    # they are useful in a few circumstances, so they are exported,
+    # but don't use them unless you are a core developer ;-)
     push @EXPORT, qw(
       &ModBiblioMarc
     );
@@ -138,16 +140,6 @@ BEGIN {
     );
 }
 
-eval {
-    if (C4::Context->ismemcached) {
-        require Memoize::Memcached;
-        import Memoize::Memcached qw(memoize_memcached);
-
-        memoize_memcached( 'GetMarcStructure',
-                            memcached => C4::Context->memcached);
-    }
-};
-
 =head1 NAME
 
 C4::Biblio - cataloging management functions
@@ -162,11 +154,11 @@ Biblio.pm contains functions for managing storage and editing of bibliographic d
 
 =item 2. as raw MARC in the Zebra index and storage engine
 
-=item 3. as raw MARC the biblioitems.marc and biblioitems.marcxml
+=item 3. as MARC XML in biblio_metadata.metadata
 
 =back
 
-In the 3.0 version of Koha, the authoritative record-level information is in biblioitems.marcxml
+In the 3.0 version of Koha, the authoritative record-level information is in biblio_metadata.metadata
 
 Because the data isn't completely normalized there's a chance for information to get out of sync. The design choice to go with a un-normalized schema was driven by performance and stability concerns. However, if this occur, it can be considered as a bug : The API is (or should be) complete & the only entry point for all biblio/items managements.
 
@@ -186,7 +178,7 @@ Because of this design choice, the process of managing storage and editing is a
 
 =item 2. _koha_* - low-level internal functions for managing the koha tables
 
-=item 3. Marc management function : as the MARC record is stored in biblioitems.marc(xml), some subs dedicated to it's management are in this package. They should be used only internally by Biblio.pm, the only official entry points being AddBiblio, AddItem, ModBiblio, ModItem.
+=item 3. Marc management function : as the MARC record is stored in biblio_metadata.metadata, some subs dedicated to it's management are in this package. They should be used only internally by Biblio.pm, the only official entry points being AddBiblio, AddItem, ModBiblio, ModItem.
 
 =item 4. Zebra functions used to update the Zebra index
 
@@ -194,7 +186,7 @@ Because of this design choice, the process of managing storage and editing is a
 
 =back
 
-The MARC record (in biblioitems.marcxml) contains the complete marc record, including items. It also contains the biblionumber. That is the reason why it is not stored directly by AddBiblio, with all other fields . To save a biblio, we need to :
+The MARC record (in biblio_metadata.metadata) contains the complete marc record, including items. It also contains the biblionumber. That is the reason why it is not stored directly by AddBiblio, with all other fields . To save a biblio, we need to :
 
 =over 4
 
@@ -206,20 +198,6 @@ The MARC record (in biblioitems.marcxml) contains the complete marc record, incl
 
 =back
 
-When dealing with items, we must :
-
-=over 4
-
-=item 1. save the item in items table, that gives us an itemnumber
-
-=item 2. add the itemnumber to the item MARC field
-
-=item 3. overwrite the MARC record (with the added item) into biblioitems.marc(xml)
-
-When modifying a biblio or an item, the behaviour is quite similar.
-
-=back
-
 =head1 EXPORTED FUNCTIONS
 
 =head2 AddBiblio
@@ -235,8 +213,8 @@ 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<bibilioitems.marc>
-and C<biblioitems.marcxml>  This option is provided B<only>
+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
@@ -263,7 +241,7 @@ sub AddBiblio {
 
     # transform the data into koha-table style data
     SetUTF8Flag($record);
-    my $olddata = TransformMarcToKoha( $dbh, $record, $frameworkcode );
+    my $olddata = TransformMarcToKoha( $record, $frameworkcode );
     ( $biblionumber, $error ) = _koha_add_biblio( $dbh, $olddata, $frameworkcode );
     $olddata->{'biblionumber'} = $biblionumber;
     ( $biblioitemnumber, $error ) = _koha_add_biblioitem( $dbh, $olddata );
@@ -315,7 +293,7 @@ sub ModBiblio {
     }
 
     if ( C4::Context->preference("CataloguingLog") ) {
-        my $newrecord = GetMarcBiblio($biblionumber);
+        my $newrecord = GetMarcBiblio({ biblionumber => $biblionumber });
         logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
     }
 
@@ -346,7 +324,7 @@ sub ModBiblio {
     _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
 
     # load the koha-table data object
-    my $oldbiblio = TransformMarcToKoha( $dbh, $record, $frameworkcode );
+    my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
 
     # update MARC subfield that stores biblioitems.cn_sort
     _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
@@ -389,30 +367,14 @@ sub _strip_item_fields {
     }
 }
 
-=head2 ModBiblioframework
-
-   ModBiblioframework($biblionumber,$frameworkcode);
-
-Exported function to modify a biblio framework
-
-=cut
-
-sub ModBiblioframework {
-    my ( $biblionumber, $frameworkcode ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare( "UPDATE biblio SET frameworkcode=? WHERE biblionumber=?" );
-    $sth->execute( $frameworkcode, $biblionumber );
-    return 1;
-}
-
 =head2 DelBiblio
 
   my $error = &DelBiblio($biblionumber);
 
 Exported function (core API) for deleting a biblio in koha.
-Deletes biblio record from Zebra and Koha tables (biblio,biblioitems,items)
-Also backs it up to deleted* tables
-Checks to make sure there are not issues on any of the items
+Deletes biblio record from Zebra and Koha tables (biblio & biblioitems)
+Also backs it up to deleted* tables.
+Checks to make sure that the biblio has no items attached.
 return:
 C<$error> : undef unless an error occurs
 
@@ -442,10 +404,11 @@ sub DelBiblio {
     }
 
     # We delete any existing holds
+    my $biblio = Koha::Biblios->find( $biblionumber );
+    my $holds = $biblio->holds;
     require C4::Reserves;
-    my $reserves = C4::Reserves::GetReservesFromBiblionumber({ biblionumber => $biblionumber });
-    foreach my $res ( @$reserves ) {
-        C4::Reserves::CancelReserve({ reserve_id => $res->{'reserve_id'} });
+    while ( my $hold = $holds->next ) {
+        C4::Reserves::CancelReserve({ reserve_id => $hold->reserve_id }); # TODO Replace with $hold->cancel
     }
 
     # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
@@ -464,6 +427,7 @@ sub DelBiblio {
         return $error if $error;
     }
 
+
     # delete biblio from Koha tables and save in deletedbiblio
     # must do this *after* _koha_delete_biblioitems, otherwise
     # delete cascade will prevent deletedbiblioitems rows
@@ -580,8 +544,7 @@ sub LinkBibHeadingsToAuthorities {
                     $results{'linked'}->{ $heading->display_form() }++;
                 }
                 else {
-                    my $authtypedata =
-                      C4::AuthoritiesMarc::GetAuthType( $heading->auth_type() );
+                    my $authority_type = Koha::Authority::Types->find( $heading->auth_type() );
                     my $marcrecordauth = MARC::Record->new();
                     if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
                         $marcrecordauth->leader('     nz  a22     o  4500');
@@ -590,7 +553,7 @@ sub LinkBibHeadingsToAuthorities {
                     $field->delete_subfield( code => '9' )
                       if defined $current_link;
                     my $authfield =
-                      MARC::Field->new( $authtypedata->{auth_tag_to_report},
+                      MARC::Field->new( $authority_type->auth_tag_to_report,
                         '', '', "a" => "" . $field->subfield('a') );
                     map {
                         $authfield->add_subfields( $_->[0] => $_->[1] )
@@ -634,6 +597,7 @@ sub LinkBibHeadingsToAuthorities {
                         $heading->auth_type() );
                     $field->add_subfields( '9', $authid );
                     $num_headings_changed++;
+                    $linker->update_cache($heading, $authid);
                     $results{'added'}->{ $heading->display_form() }++;
                 }
             }
@@ -663,7 +627,7 @@ sub LinkBibHeadingsToAuthorities {
     }
 
 Check whether the specified heading-auth link is valid without reference
-to Zebra/Solr. Ideally this code would be in C4::Heading, but that won't be
+to Zebra. Ideally this code would be in C4::Heading, but that won't be
 possible until we have de-cycled C4::AuthoritiesMarc, so this is the
 safest place.
 
@@ -718,66 +682,6 @@ sub GetRecordValue {
     return \@result;
 }
 
-=head2 SetFieldMapping
-
-  SetFieldMapping($framework, $field, $fieldcode, $subfieldcode);
-
-Set a Field to MARC mapping value, if it already exists we don't add a new one.
-
-=cut
-
-sub SetFieldMapping {
-    my ( $framework, $field, $fieldcode, $subfieldcode ) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $sth = $dbh->prepare('SELECT * FROM fieldmapping WHERE fieldcode = ? AND subfieldcode = ? AND frameworkcode = ? AND field = ?');
-    $sth->execute( $fieldcode, $subfieldcode, $framework, $field );
-    if ( not $sth->fetchrow_hashref ) {
-        my @args;
-        $sth = $dbh->prepare('INSERT INTO fieldmapping (fieldcode, subfieldcode, frameworkcode, field) VALUES(?,?,?,?)');
-
-        $sth->execute( $fieldcode, $subfieldcode, $framework, $field );
-    }
-}
-
-=head2 DeleteFieldMapping
-
-  DeleteFieldMapping($id);
-
-Delete a field mapping from an $id.
-
-=cut
-
-sub DeleteFieldMapping {
-    my ($id) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $sth = $dbh->prepare('DELETE FROM fieldmapping WHERE id = ?');
-    $sth->execute($id);
-}
-
-=head2 GetFieldMapping
-
-  GetFieldMapping($frameworkcode);
-
-Get all field mappings for a specified frameworkcode
-
-=cut
-
-sub GetFieldMapping {
-    my ($framework) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $sth = $dbh->prepare('SELECT * FROM fieldmapping where frameworkcode = ?');
-    $sth->execute($framework);
-
-    my @return;
-    while ( my $row = $sth->fetchrow_hashref ) {
-        push @return, $row;
-    }
-    return \@return;
-}
-
 =head2 GetBiblioData
 
   $data = &GetBiblioData($biblionumber);
@@ -865,77 +769,33 @@ sub GetBiblioItemByBiblioNumber {
     return @results;
 }
 
-=head2 GetBiblionumberFromItemnumber
-
-
-=cut
-
-sub GetBiblionumberFromItemnumber {
-    my ($itemnumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare("Select biblionumber FROM items WHERE itemnumber = ?");
-
-    $sth->execute($itemnumber);
-    my ($result) = $sth->fetchrow;
-    return ($result);
-}
-
-=head2 GetBiblioFromItemNumber
-
-  $item = &GetBiblioFromItemNumber($itemnumber,$barcode);
-
-Looks up the item with the given itemnumber. if undef, try the barcode.
-
-C<&itemnodata> returns a reference-to-hash whose keys are the fields
-from the C<biblio>, C<biblioitems>, and C<items> tables in the Koha
-database.
-
-=cut
-
-#'
-sub GetBiblioFromItemNumber {
-    my ( $itemnumber, $barcode ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth;
-    if ($itemnumber) {
-        $sth = $dbh->prepare(
-            "SELECT * FROM items 
-            LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
-            LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
-             WHERE items.itemnumber = ?"
-        );
-        $sth->execute($itemnumber);
-    } else {
-        $sth = $dbh->prepare(
-            "SELECT * FROM items 
-            LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
-            LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
-             WHERE items.barcode = ?"
-        );
-        $sth->execute($barcode);
-    }
-    my $data = $sth->fetchrow_hashref;
-    $sth->finish;
-    return ($data);
-}
-
 =head2 GetISBDView 
 
-  $isbd = &GetISBDView($biblionumber);
+  $isbd = &GetISBDView({
+      'record'    => $marc_record,
+      'template'  => $interface, # opac/intranet
+      'framework' => $framework,
+  });
 
 Return the ISBD view which can be included in opac and intranet
 
 =cut
 
 sub GetISBDView {
-    my ( $biblionumber, $template ) = @_;
-    my $record   = GetMarcBiblio($biblionumber, 1);
+    my ( $params ) = @_;
+
+    # Expecting record WITH items.
+    my $record    = $params->{record};
     return unless defined $record;
-    my $itemtype = &GetFrameworkCode($biblionumber);
+
+    my $template  = $params->{template} // q{};
+    my $sysprefname = $template eq 'opac' ? 'opacisbd' : 'isbd';
+    my $framework = $params->{framework};
+    my $itemtype  = $framework;
     my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch", $itemtype );
-    my $tagslib = &GetMarcStructure( 1, $itemtype );
+    my $tagslib = &GetMarcStructure( 1, $itemtype, { unsafe => 1 } );
 
-    my $ISBD = C4::Context->preference('isbd');
+    my $ISBD = C4::Context->preference($sysprefname);
     my $bloc = $ISBD;
     my $res;
     my $blocres;
@@ -1045,25 +905,6 @@ sub GetISBDView {
     return $res;
 }
 
-=head2 GetBiblio
-
-  my $biblio = &GetBiblio($biblionumber);
-
-=cut
-
-sub GetBiblio {
-    my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare("SELECT * FROM biblio WHERE biblionumber = ?");
-    my $count          = 0;
-    my @results;
-    $sth->execute($biblionumber);
-    if ( my $data = $sth->fetchrow_hashref ) {
-        return $data;
-    }
-    return;
-}    # sub GetBiblio
-
 =head2 GetBiblioItemInfosOf
 
   GetBiblioItemInfosOf(@biblioitemnumbers);
@@ -1073,46 +914,69 @@ sub GetBiblio {
 sub GetBiblioItemInfosOf {
     my @biblioitemnumbers = @_;
 
-    my $query = '
+    my $biblioitemnumber_values = @biblioitemnumbers ? join( ',', @biblioitemnumbers ) : "''";
+
+    my $dbh = C4::Context->dbh;
+    my $query = "
         SELECT biblioitemnumber,
             publicationyear,
             itemtype
         FROM biblioitems
-        WHERE biblioitemnumber IN (' . join( ',', @biblioitemnumbers ) . ')
-    ';
-    return get_infos_of( $query, 'biblioitemnumber' );
+        WHERE biblioitemnumber IN ($biblioitemnumber_values)
+    ";
+    return $dbh->selectall_hashref($query, 'biblioitemnumber');
 }
 
 =head1 FUNCTIONS FOR HANDLING MARC MANAGEMENT
 
+=head2 IsMarcStructureInternal
+
+    my $tagslib = C4::Biblio::GetMarcStructure();
+    for my $tag ( sort keys %$tagslib ) {
+        next unless $tag;
+        for my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
+            next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
+        }
+        # Process subfield
+    }
+
+GetMarcStructure creates keys (lib, tab, mandatory, repeatable) for a display purpose.
+These different values should not be processed as valid subfields.
+
+=cut
+
+sub IsMarcStructureInternal {
+    my ( $subfield ) = @_;
+    return ref $subfield ? 0 : 1;
+}
+
 =head2 GetMarcStructure
 
-  $res = GetMarcStructure($forlibrarian,$frameworkcode);
+  $res = GetMarcStructure($forlibrarian, $frameworkcode, [ $params ]);
 
 Returns a reference to a big hash of hash, with the Marc structure for the given frameworkcode
 $forlibrarian  :if set to 1, the MARC descriptions are the librarians ones, otherwise it's the public (OPAC) ones
 $frameworkcode : the framework code to read
+$params allows you to pass { unsafe => 1 } for better performance.
 
-=cut
+Note: If you call GetMarcStructure with unsafe => 1, do not modify or
+even autovivify its contents. It is a cached/shared data structure. Your
+changes c/would be passed around in subsequent calls.
 
-# cache for results of GetMarcStructure -- needed
-# for batch jobs
-our $marc_structure_cache;
+=cut
 
 sub GetMarcStructure {
-    my ( $forlibrarian, $frameworkcode ) = @_;
-    my $dbh = C4::Context->dbh;
+    my ( $forlibrarian, $frameworkcode, $params ) = @_;
     $frameworkcode = "" unless $frameworkcode;
 
-    if ( defined $marc_structure_cache and exists $marc_structure_cache->{$forlibrarian}->{$frameworkcode} ) {
-        return $marc_structure_cache->{$forlibrarian}->{$frameworkcode};
-    }
+    $forlibrarian = $forlibrarian ? 1 : 0;
+    my $unsafe = ($params && $params->{unsafe})? 1: 0;
+    my $cache = Koha::Caches->get_instance();
+    my $cache_key = "MarcStructure-$forlibrarian-$frameworkcode";
+    my $cached = $cache->get_from_cache($cache_key, { unsafe => $unsafe });
+    return $cached if $cached;
 
-    #     my $sth = $dbh->prepare(
-    #         "SELECT COUNT(*) FROM marc_tag_structure WHERE frameworkcode=?");
-    #     $sth->execute($frameworkcode);
-    #     my ($total) = $sth->fetchrow;
-    #     $frameworkcode = "" unless ( $total > 0 );
+    my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare(
         "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable 
         FROM marc_tag_structure 
@@ -1174,8 +1038,7 @@ sub GetMarcStructure {
         $res->{$tag}->{$subfield}->{maxlength}        = $maxlength;
     }
 
-    $marc_structure_cache->{$forlibrarian}->{$frameworkcode} = $res;
-
+    $cache->set_in_cache($cache_key, $res);
     return $res;
 }
 
@@ -1195,18 +1058,44 @@ C<$frameworkcode> is the framework code.
 
 sub GetUsedMarcStructure {
     my $frameworkcode = shift || '';
-    my $query = qq/
+    my $query = q{
         SELECT *
         FROM   marc_subfield_structure
         WHERE   tab > -1 
             AND frameworkcode = ?
         ORDER BY tagfield, tagsubfield
-    /;
+    };
     my $sth = C4::Context->dbh->prepare($query);
     $sth->execute($frameworkcode);
     return $sth->fetchall_arrayref( {} );
 }
 
+=head2 GetMarcSubfieldStructure
+
+=cut
+
+sub GetMarcSubfieldStructure {
+    my ( $frameworkcode ) = @_;
+
+    $frameworkcode //= '';
+
+    my $cache     = Koha::Caches->get_instance();
+    my $cache_key = "MarcSubfieldStructure-$frameworkcode";
+    my $cached    = $cache->get_from_cache($cache_key);
+    return $cached if $cached;
+
+    my $dbh = C4::Context->dbh;
+    my $subfield_structure = $dbh->selectall_hashref( q|
+        SELECT *
+        FROM marc_subfield_structure
+        WHERE frameworkcode = ?
+        AND kohafield > ''
+    |, 'kohafield', {}, $frameworkcode );
+
+    $cache->set_in_cache( $cache_key, $subfield_structure );
+    return $subfield_structure;
+}
+
 =head2 GetMarcFromKohaField
 
   ($MARCfield,$MARCsubfield)=GetMarcFromKohaField($kohafield,$frameworkcode);
@@ -1217,14 +1106,10 @@ for the given frameworkcode or default framework if $frameworkcode is missing
 =cut
 
 sub GetMarcFromKohaField {
-    my $kohafield = shift;
-    my $frameworkcode = shift || '';
+    my ( $kohafield, $frameworkcode ) = @_;
     return (0, undef) unless $kohafield;
-    my $relations = C4::Context->marcfromkohafield;
-    if ( my $mf = $relations->{$frameworkcode}->{$kohafield} ) {
-        return @$mf;
-    }
-    return (0, undef);
+    my $mss = GetMarcSubfieldStructure( $frameworkcode );
+    return ( $mss->{$kohafield}{tagfield}, $mss->{$kohafield}{tagsubfield} );
 }
 
 =head2 GetMarcSubfieldStructureFromKohaField
@@ -1239,58 +1124,93 @@ $frameworkcode is optional. If not given, then the default framework is used.
 =cut
 
 sub GetMarcSubfieldStructureFromKohaField {
-    my ($kohafield, $frameworkcode) = @_;
+    my ( $kohafield, $frameworkcode ) = @_;
 
-    return undef unless $kohafield;
-    $frameworkcode //= '';
+    return unless $kohafield;
 
-    my $dbh = C4::Context->dbh;
-    my $query = qq{
-        SELECT *
-        FROM marc_subfield_structure
-        WHERE kohafield = ?
-          AND frameworkcode = ?
-    };
-    my $sth = $dbh->prepare($query);
-    $sth->execute($kohafield, $frameworkcode);
-    my $result = $sth->fetchrow_hashref;
-    $sth->finish;
-
-    return $result;
+    my $mss = GetMarcSubfieldStructure( $frameworkcode );
+    return exists $mss->{$kohafield}
+        ? $mss->{$kohafield}
+        : undef;
 }
 
 =head2 GetMarcBiblio
 
-  my $record = GetMarcBiblio($biblionumber, [$embeditems]);
+  my $record = GetMarcBiblio({
+      biblionumber => $biblionumber,
+      embed_items  => $embeditems,
+      opac         => $opac });
+
+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.
 
-Returns MARC::Record representing bib identified by
-C<$biblionumber>.  If no bib exists, returns undef.
-C<$embeditems>.  If set to true, items data are included.
-The MARC record contains biblio data, and items data if $embeditems is set to true.
+=item C<$opac>
+
+set to true to make the result suited for OPAC view. This causes things like
+OpacHiddenItems to be applied.
+
+=back
 
 =cut
 
 sub GetMarcBiblio {
-    my $biblionumber = shift;
-    my $embeditems   = shift || 0;
+    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;
+
+    if (not defined $biblionumber) {
+        carp 'GetMarcBiblio called with undefined biblionumber';
+        return;
+    }
+
     my $dbh          = C4::Context->dbh;
-    my $sth          = $dbh->prepare("SELECT marcxml FROM biblioitems WHERE biblionumber=? ");
+    my $sth          = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=? ");
     $sth->execute($biblionumber);
     my $row     = $sth->fetchrow_hashref;
-    my $marcxml = StripNonXmlChars( $row->{'marcxml'} );
+    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') ) };
+        $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, '', $biblionumber, $biblionumber);
-       C4::Biblio::EmbedItemsInMarcBiblio($record, $biblionumber) if ($embeditems);
+        C4::Biblio::_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber,
+            $biblioitemnumber );
+        C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, $opac )
+          if ($embeditems);
 
         return $record;
-    } else {
+    }
+    else {
         return;
     }
 }
@@ -1299,17 +1219,24 @@ sub GetMarcBiblio {
 
   my $marcxml = GetXmlBiblio($biblionumber);
 
-Returns biblioitems.marcxml of the biblionumber passed in parameter.
+Returns biblio_metadata.metadata/marcxml of the biblionumber passed in parameter.
 The XML should only contain biblio information (item information is no longer stored in marcxml field)
 
 =cut
 
 sub GetXmlBiblio {
     my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare("SELECT marcxml FROM biblioitems WHERE biblionumber=? ");
-    $sth->execute($biblionumber);
-    my ($marcxml) = $sth->fetchrow;
+    my $dbh = C4::Context->dbh;
+    return unless $biblionumber;
+    my ($marcxml) = $dbh->selectrow_array(
+        q|
+        SELECT metadata
+        FROM biblio_metadata
+        WHERE biblionumber=?
+            AND format='marcxml'
+            AND marcflavour=?
+    |, undef, $biblionumber, C4::Context->preference('marcflavour')
+    );
     return $marcxml;
 }
 
@@ -1485,7 +1412,7 @@ sub GetMarcPrice {
     my @listtags;
     my $subfield;
     
-    if ( $marcflavour eq "MARC21" ) {
+    if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
         @listtags = ('345', '020');
         $subfield="c";
     } elsif ( $marcflavour eq "UNIMARC" ) {
@@ -1508,42 +1435,59 @@ sub GetMarcPrice {
 =head2 MungeMarcPrice
 
 Return the best guess at what the actual price is from a price field.
+
 =cut
 
 sub MungeMarcPrice {
     my ( $price ) = @_;
-
     return unless ( $price =~ m/\d/ ); ## No digits means no price.
-
-    ## Look for the currency symbol of the active currency, if it's there,
-    ## start the price string right after the symbol. This allows us to prefer
-    ## this native currency price over other currency prices, if possible.
-    my $active_currency = C4::Context->dbh->selectrow_hashref( 'SELECT * FROM currency WHERE active = 1', {} );
-    my $symbol = quotemeta( $active_currency->{'symbol'} );
-    if ( $price =~ m/$symbol/ ) {
-        my @parts = split(/$symbol/, $price );
-        $price = $parts[1];
-    }
-
-    ## Grab the first number in the string ( can use commas or periods for thousands separator and/or decimal separator )
-    ( $price ) = $price =~ m/([\d\,\.]+[[\,\.]\d\d]?)/;
-
-    ## Split price into array on periods and commas
-    my @parts = split(/[\,\.]/, $price);
-
-    ## If the last grouping of digits is more than 2 characters, assume there is no decimal value and put it back.
-    my $decimal = pop( @parts );
-    if ( length( $decimal ) > 2 ) {
-        push( @parts, $decimal );
-        $decimal = '';
-    }
-
-    $price = join('', @parts );
-
-    if ( $decimal ) {
-     $price .= ".$decimal";
+    # Look for the currency symbol and the normalized code of the active currency, if it's there,
+    my $active_currency = Koha::Acquisition::Currencies->get_active;
+    my $symbol = $active_currency->symbol;
+    my $isocode = $active_currency->isocode;
+    $isocode = $active_currency->currency unless defined $isocode;
+    my $localprice;
+    if ( $symbol ) {
+        my @matches =($price=~ /
+            \s?
+            (                          # start of capturing parenthesis
+            (?:
+            (?:[\p{Sc}\p{L}\/.]){1,4}  # any character from Currency signs or Letter Unicode categories or slash or dot                                              within 1 to 4 occurrences : call this whole block 'symbol block'
+            |(?:\d+[\p{P}\s]?){1,4}    # or else at least one digit followed or not by a punctuation sign or whitespace,                                             all these within 1 to 4 occurrences : call this whole block 'digits block'
+            )
+            \s?\p{Sc}?\s?              # followed or not by a whitespace. \p{Sc}?\s? are for cases like '25$ USD'
+            (?:
+            (?:[\p{Sc}\p{L}\/.]){1,4}  # followed by same block as symbol block
+            |(?:\d+[\p{P}\s]?){1,4}    # or by same block as digits block
+            )
+            \s?\p{L}{0,4}\s?           # followed or not by a whitespace. \p{L}{0,4}\s? are for cases like '$9.50 USD'
+            )                          # end of capturing parenthesis
+            (?:\p{P}|\z)               # followed by a punctuation sign or by the end of the string
+            /gx);
+
+        if ( @matches ) {
+            foreach ( @matches ) {
+                $localprice = $_ and last if index($_, $isocode)>=0;
+            }
+            if ( !$localprice ) {
+                foreach ( @matches ) {
+                    $localprice = $_ and last if $_=~ /(^|[^\p{Sc}\p{L}\/])\Q$symbol\E([^\p{Sc}\p{L}\/]+\z|\z)/;
+                }
+            }
+        }
     }
-
+    if ( $localprice ) {
+        $price = $localprice;
+    } else {
+        ## Grab the first number in the string ( can use commas or periods for thousands separator and/or decimal separator )
+        ( $price ) = $price =~ m/([\d\,\.]+[[\,\.]\d\d]?)/;
+    }
+    # eliminate symbol/isocode, space and any final dot from the string
+    $price =~ s/[\p{Sc}\p{L}\/ ]|\.$//g;
+    # remove comma,dot when used as separators from hundreds
+    $price =~s/[\,\.](\d{3})/$1/g;
+    # convert comma to dot to ensure correct display of decimals if existing
+    $price =~s/,/./;
     return $price;
 }
 
@@ -1612,7 +1556,6 @@ descriptions rather than normal ones when they exist.
 
 sub GetAuthorisedValueDesc {
     my ( $tag, $subfield, $value, $framework, $tagslib, $category, $opac ) = @_;
-    my $dbh = C4::Context->dbh;
 
     if ( !$category ) {
 
@@ -1620,18 +1563,20 @@ sub GetAuthorisedValueDesc {
 
         #---- branch
         if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
-            return C4::Branch::GetBranchName($value);
+            return Koha::Libraries->find($value)->branchname;
         }
 
         #---- itemtypes
         if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
-            return getitemtypeinfo($value)->{description};
+            my $itemtype = Koha::ItemTypes->find( $value );
+            return $itemtype ? $itemtype->translated_description : q||;
         }
 
         #---- "true" authorized value
         $category = $tagslib->{$tag}->{$subfield}->{'authorised_value'};
     }
 
+    my $dbh = C4::Context->dbh;
     if ( $category ne "" ) {
         my $sth = $dbh->prepare( "SELECT lib, lib_opac FROM authorised_values WHERE category = ? AND authorised_value = ?" );
         $sth->execute( $category, $value );
@@ -1692,7 +1637,7 @@ sub GetMarcISBN {
 
     my @marcisbns;
     foreach my $field ( $record->field($scope) ) {
-        my $isbn = $field->as_string();
+        my $isbn = $field->subfield( 'a' );
         if ( $isbn ne "" ) {
             push @marcisbns, $isbn;
         }
@@ -1726,17 +1671,19 @@ sub GetMarcISSN {
     }
     my @marcissns;
     foreach my $field ( $record->field($scope) ) {
-        push @marcissns, $field->subfield( 'a' );
+        push @marcissns, $field->subfield( 'a' )
+            if ( $field->subfield( 'a' ) ne "" );
     }
     return \@marcissns;
 }    # end GetMarcISSN
 
 =head2 GetMarcNotes
 
-  $marcnotesarray = GetMarcNotes( $record, $marcflavour );
+    $marcnotesarray = GetMarcNotes( $record, $marcflavour );
 
-Get all notes from the MARC record and returns them in an array.
-The note are stored in different fields depending on MARC flavour
+    Get all notes from the MARC record and returns them in an array.
+    The notes are stored in different fields depending on MARC flavour.
+    MARC21 field 555 gets special attention for the $u subfields.
 
 =cut
 
@@ -1746,38 +1693,28 @@ sub GetMarcNotes {
         carp 'GetMarcNotes called on undefined record';
         return;
     }
-    my $scope;
-    if ( $marcflavour eq "UNIMARC" ) {
-        $scope = '3..';
-    } else {    # assume marc21 if not unimarc
-        $scope = '5..';
-    }
+
+    my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
     my @marcnotes;
-    my $note = "";
-    my $tag  = "";
-    my $marcnote;
-    my %blacklist = map { $_ => 1 } split(/,/,C4::Context->preference('NotesBlacklist'));
+    my %blacklist = map { $_ => 1 }
+        split( /,/, C4::Context->preference('NotesBlacklist'));
     foreach my $field ( $record->field($scope) ) {
         my $tag = $field->tag();
-        if (!$blacklist{$tag}) {
-            my $value = $field->as_string();
-            if ( $note ne "" ) {
-                $marcnote = { marcnote => $note, };
-                push @marcnotes, $marcnote;
-                $note = $value;
-            }
-            if ( $note ne $value ) {
-                $note = $note . " " . $value;
+        next if $blacklist{ $tag };
+        if( $marcflavour ne 'UNIMARC' && $tag =~ /555/ ) {
+            # Field 555$u contains URLs
+            # We first push the regular subfields and all $u's separately
+            # Leave further actions to the template
+            push @marcnotes, { marcnote => $field->as_string('abcd') };
+            foreach my $sub ( $field->subfield('u') ) {
+                push @marcnotes, { marcnote => $sub };
             }
+        } else {
+            push @marcnotes, { marcnote => $field->as_string() };
         }
     }
-
-    if ($note) {
-        $marcnote = { marcnote => $note };
-        push @marcnotes, $marcnote;    #load last tag into array
-    }
     return \@marcnotes;
-}    # end GetMarcNotes
+}
 
 =head2 GetMarcSubjects
 
@@ -1808,7 +1745,7 @@ sub GetMarcSubjects {
     my @marcsubjects;
 
     my $subject_limit = C4::Context->preference("TraceCompleteSubfields") ? 'su,complete-subfield' : 'su';
-    my $authoritysep = C4::Context->preference('authoritysep');
+    my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
 
     foreach my $field ( $record->field($fields_filter) ) {
         next unless ($field->tag() >= $mintag && $field->tag() <= $maxtag);
@@ -1854,7 +1791,7 @@ sub GetMarcSubjects {
                     code      => $code,
                     value     => $value,
                     link_loop => \@this_link_loop,
-                    separator => (scalar @subfields_loop) ? $authoritysep : ''
+                    separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
                 };
             }
         }
@@ -1862,7 +1799,7 @@ sub GetMarcSubjects {
         push @marcsubjects, {
             MARCSUBJECT_SUBFIELDS_LOOP => \@subfields_loop,
             authoritylink => $authoritylink,
-        };
+        } if $authoritylink || @subfields_loop;
 
     }
     return \@marcsubjects;
@@ -1885,10 +1822,11 @@ sub GetMarcAuthors {
     }
     my ( $mintag, $maxtag, $fields_filter );
 
-    # tagslib useful for UNIMARC author reponsabilities
-    my $tagslib =
-      &GetMarcStructure( 1, '' );    # FIXME : we don't have the framework available, we take the default framework. May be buggy on some setups, will be usually correct.
+    # 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..';
@@ -1899,7 +1837,7 @@ sub GetMarcAuthors {
     }
 
     my @marcauthors;
-    my $authoritysep = C4::Context->preference('authoritysep');
+    my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
 
     foreach my $field ( $record->field($fields_filter) ) {
         next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
@@ -1917,9 +1855,14 @@ sub GetMarcAuthors {
         }
 
         # 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/ ) );
 
@@ -1948,13 +1891,14 @@ sub GetMarcAuthors {
                     code      => $code,
                     value     => $value,
                     link_loop => \@this_link_loop,
-                    separator => (scalar @subfields_loop) ? $authoritysep : ''
+                    separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
                 };
             }
         }
         push @marcauthors, {
             MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop,
             authoritylink => $subfield9,
+            unimarc3 => $unimarc3
         };
     }
     return \@marcauthors;
@@ -1984,6 +1928,7 @@ sub GetMarcUrls {
         }
         my @urls = $field->subfield('u');
         foreach my $url (@urls) {
+            $url =~ s/^\s+|\s+$//g; # trim
             my $marcurl;
             if ( $marcflavour eq 'MARC21' ) {
                 my $s3   = $field->subfield('3');
@@ -2047,7 +1992,7 @@ sub GetMarcSeries {
     }
 
     my @marcseries;
-    my $authoritysep = C4::Context->preference('authoritysep');
+    my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
 
     foreach my $field ( $record->field($fields_filter) ) {
         next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
@@ -2083,7 +2028,7 @@ sub GetMarcSeries {
                     code      => $code,
                     value     => $value,
                     link_loop => \@link_loop,
-                    separator => (scalar @subfields_loop) ? $authoritysep : '',
+                    separator => (scalar @subfields_loop) ? $AuthoritySeparator : '',
                     volumenum => $volume_number,
                 }
             }
@@ -2141,6 +2086,45 @@ sub GetMarcHosts {
     return $marchostsarray;
 }
 
+=head2 UpsertMarcSubfield
+
+    my $record = C4::Biblio::UpsertMarcSubfield($MARC::Record, $fieldTag, $subfieldCode, $subfieldContent);
+
+=cut
+
+sub UpsertMarcSubfield {
+    my ($record, $tag, $code, $content) = @_;
+    my $f = $record->field($tag);
+
+    if ($f) {
+        $f->update( $code => $content );
+    }
+    else {
+        my $f = MARC::Field->new( $tag, '', '', $code => $content);
+        $record->insert_fields_ordered( $f );
+    }
+}
+
+=head2 UpsertMarcControlField
+
+    my $record = C4::Biblio::UpsertMarcControlField($MARC::Record, $fieldTag, $content);
+
+=cut
+
+sub UpsertMarcControlField {
+    my ($record, $tag, $content) = @_;
+    die "UpsertMarcControlField() \$tag '$tag' is not a control field\n" unless 0+$tag < 10;
+    my $f = $record->field($tag);
+
+    if ($f) {
+        $f->update( $content );
+    }
+    else {
+        my $f = MARC::Field->new($tag, $content);
+        $record->insert_fields_ordered( $f );
+    }
+}
+
 =head2 GetFrameworkCode
 
   $frameworkcode = GetFrameworkCode( $biblionumber )
@@ -2173,21 +2157,27 @@ sub TransformKohaToMarc {
     my $hash = shift;
     my $record = MARC::Record->new();
     SetMarcUnicodeFlag( $record, C4::Context->preference("marcflavour") );
-    my $db_to_marc = C4::Context->marcfromkohafield;
-    while ( my ($name, $value) = each %$hash ) {
-        next unless my $dtm = $db_to_marc->{''}->{$name};
-        next unless ( scalar( @$dtm ) );
-        my ($tag, $letter) = @$dtm;
+    # FIXME Do not we want to get the marc subfield structure for the biblio framework?
+    my $mss = GetMarcSubfieldStructure();
+    my $tag_hr = {};
+    while ( my ($kohafield, $value) = each %$hash ) {
+        next unless exists $mss->{$kohafield};
+        next unless $mss->{$kohafield};
+        my $tagfield    = $mss->{$kohafield}{tagfield} . '';
+        my $tagsubfield = $mss->{$kohafield}{tagsubfield};
         foreach my $value ( split(/\s?\|\s?/, $value, -1) ) {
-            if ( my $field = $record->field($tag) ) {
-                $field->add_subfields( $letter => $value );
-            }
-            else {
-                $record->insert_fields_ordered( MARC::Field->new(
-                    $tag, " ", " ", $letter => $value ) );
-            }
+            next if $value eq '';
+            $tag_hr->{$tagfield} //= [];
+            push @{$tag_hr->{$tagfield}}, [($tagsubfield, $value)];
         }
-
+    }
+    foreach my $tag (sort keys %$tag_hr) {
+        my @sfl = @{$tag_hr->{$tag}};
+        @sfl = sort { $a->[0] cmp $b->[0]; } @sfl;
+        @sfl = map { @{$_}; } @sfl;
+        $record->insert_fields_ordered(
+            MARC::Field->new($tag, " ", " ", @sfl)
+        );
     }
     return $record;
 }
@@ -2205,7 +2195,7 @@ sub PrepHostMarcField {
     $marcflavour ||="MARC21";
     
     require C4::Items;
-    my $hostrecord = GetMarcBiblio($hostbiblionumber);
+    my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
        my $item = C4::Items::GetItem($hostitemnumber);
        
        my $hostmarcfield;
@@ -2293,6 +2283,8 @@ $auth_type contains :
 
 sub TransformHtmlToXml {
     my ( $tags, $subfields, $values, $indicator, $ind_tag, $auth_type ) = @_;
+    # NOTE: The parameter $ind_tag is NOT USED -- BZ 11247
+
     my $xml = MARC::File::XML::header('UTF-8');
     $xml .= "<record>\n";
     $auth_type = C4::Context->preference('marcflavour') unless $auth_type;
@@ -2326,9 +2318,6 @@ sub TransformHtmlToXml {
         @$values[$i] =~ s/"/&quot;/g;
         @$values[$i] =~ s/'/&apos;/g;
 
-        #         if ( !utf8::is_utf8( @$values[$i] ) ) {
-        #             utf8::decode( @$values[$i] );
-        #         }
         if ( ( @$tags[$i] ne $prevtag ) ) {
             $j++ unless ( @$tags[$i] eq "" );
             my $indicator1 = eval { substr( @$indicator[$j], 0, 1 ) };
@@ -2452,9 +2441,9 @@ sub _default_ind_to_space {
 =cut
 
 sub TransformHtmlToMarc {
-    my $cgi    = shift;
+    my ($cgi, $isbiblio) = @_;
 
-    my @params = $cgi->param();
+    my @params = $cgi->multi_param();
 
     # explicitly turn on the UTF-8 flag for all
     # 'tag_' parameters to avoid incorrect character
@@ -2463,30 +2452,29 @@ sub TransformHtmlToMarc {
     foreach my $param_name ( keys %$cgi_params ) {
         if ( $param_name =~ /^tag_/ ) {
             my $param_value = $cgi_params->{$param_name};
-            if ( utf8::decode($param_value) ) {
-                $cgi_params->{$param_name} = $param_value;
+            unless ( Encode::is_utf8( $param_value ) ) {
+                $cgi_params->{$param_name} = Encode::decode('UTF-8', $param_value );
             }
-
-            # FIXME - need to do something if string is not valid UTF-8
         }
     }
 
     # creating a new record
     my $record = MARC::Record->new();
-    my $i      = 0;
     my @fields;
+    my ($biblionumbertagfield, $biblionumbertagsubfield) = (-1, -1);
+    ($biblionumbertagfield, $biblionumbertagsubfield) =
+        &GetMarcFromKohaField( "biblio.biblionumber", '' ) if $isbiblio;
 #FIXME This code assumes that the CGI params will be in the same order as the fields in the template; this is no absolute guarantee!
-    while ( $params[$i] ) {    # browse all CGI params
+    for (my $i = 0; $params[$i]; $i++ ) {    # browse all CGI params
         my $param    = $params[$i];
         my $newfield = 0;
 
         # if we are on biblionumber, store it in the MARC::Record (it may not be in the edited fields)
         if ( $param eq 'biblionumber' ) {
-            my ( $biblionumbertagfield, $biblionumbertagsubfield ) = &GetMarcFromKohaField( "biblio.biblionumber", '' );
             if ( $biblionumbertagfield < 10 ) {
-                $newfield = MARC::Field->new( $biblionumbertagfield, $cgi->param($param), );
+                $newfield = MARC::Field->new( $biblionumbertagfield, scalar $cgi->param($param), );
             } else {
-                $newfield = MARC::Field->new( $biblionumbertagfield, '', '', "$biblionumbertagsubfield" => $cgi->param($param), );
+                $newfield = MARC::Field->new( $biblionumbertagfield, '', '', "$biblionumbertagsubfield" => scalar $cgi->param($param), );
             }
             push @fields, $newfield if ($newfield);
         } elsif ( $param =~ /^tag_(\d*)_indicator1_/ ) {    # new field start when having 'input name="..._indicator1_..."
@@ -2499,18 +2487,20 @@ sub TransformHtmlToMarc {
 
             if ( $tag < 10 ) {                              # no code for theses fields
                                                             # in MARC editor, 000 contains the leader.
+                next if $tag == $biblionumbertagfield;
+                my $fval= $cgi->param($params[$j+1]);
                 if ( $tag eq '000' ) {
                     # Force a fake leader even if not provided to avoid crashing
                     # during decoding MARC record containing UTF-8 characters
                     $record->leader(
-                        length( $cgi->param($params[$j+1]) ) == 24
-                        ? $cgi->param( $params[ $j + 1 ] )
+                        length( $fval ) == 24
+                        ? $fval
                         : '     nam a22        4500'
                        )
                     ;
                     # between 001 and 009 (included)
-                } elsif ( $cgi->param( $params[ $j + 1 ] ) ne '' ) {
-                    $newfield = MARC::Field->new( $tag, $cgi->param( $params[ $j + 1 ] ), );
+                } elsif ( $fval ne '' ) {
+                    $newfield = MARC::Field->new( $tag, $fval, );
                 }
 
                 # > 009, deal with subfields
@@ -2518,16 +2508,20 @@ sub TransformHtmlToMarc {
                 # browse subfields for this tag (reason for _code_ match)
                 while(defined $params[$j] && $params[$j] =~ /_code_/) {
                     last unless defined $params[$j+1];
+                    $j += 2 and next
+                        if $tag == $biblionumbertagfield and
+                           $cgi->param($params[$j]) eq $biblionumbertagsubfield;
                     #if next param ne subfield, then it was probably empty
                     #try next param by incrementing j
                     if($params[$j+1]!~/_subfield_/) {$j++; next; }
+                    my $fkey= $cgi->param($params[$j]);
                     my $fval= $cgi->param($params[$j+1]);
                     #check if subfield value not empty and field exists
                     if($fval ne '' && $newfield) {
-                        $newfield->add_subfields( $cgi->param($params[$j]) => $fval);
+                        $newfield->add_subfields( $fkey => $fval);
                     }
                     elsif($fval ne '') {
-                        $newfield = MARC::Field->new( $tag, $ind1, $ind2, $cgi->param($params[$j]) => $fval );
+                        $newfield = MARC::Field->new( $tag, $ind1, $ind2, $fkey => $fval );
                     }
                     $j += 2;
                 } #end-of-while
@@ -2535,19 +2529,15 @@ sub TransformHtmlToMarc {
             }
             push @fields, $newfield if ($newfield);
         }
-        $i++;
     }
 
     $record->append_fields(@fields);
     return $record;
 }
 
-# cache inverted MARC field map
-our $inverted_field_map;
-
 =head2 TransformMarcToKoha
 
-  $result = TransformMarcToKoha( $dbh, $record, $frameworkcode )
+  $result = TransformMarcToKoha( $record, $frameworkcode )
 
 Extract data from a MARC bib record into a hashref representing
 Koha biblio, biblioitems, and items fields. 
@@ -2558,7 +2548,7 @@ hash_ref
 =cut
 
 sub TransformMarcToKoha {
-    my ( $dbh, $record, $frameworkcode, $limit_table ) = @_;
+    my ( $record, $frameworkcode, $limit_table ) = @_;
 
     my $result = {};
     if (!defined $record) {
@@ -2568,9 +2558,7 @@ sub TransformMarcToKoha {
     $limit_table = $limit_table || 0;
     $frameworkcode = '' unless defined $frameworkcode;
 
-    unless ( defined $inverted_field_map ) {
-        $inverted_field_map = _get_inverted_marc_field_map();
-    }
+    my $inverted_field_map = _get_inverted_marc_field_map($frameworkcode);
 
     my %tables = ();
     if ( defined $limit_table && $limit_table eq 'items' ) {
@@ -2584,9 +2572,9 @@ sub TransformMarcToKoha {
     # traverse through record
   MARCFIELD: foreach my $field ( $record->fields() ) {
         my $tag = $field->tag();
-        next MARCFIELD unless exists $inverted_field_map->{$frameworkcode}->{$tag};
+        next MARCFIELD unless exists $inverted_field_map->{$tag};
         if ( $field->is_control_field() ) {
-            my $kohafields = $inverted_field_map->{$frameworkcode}->{$tag}->{list};
+            my $kohafields = $inverted_field_map->{$tag}->{list};
           ENTRY: foreach my $entry ( @{$kohafields} ) {
                 my ( $subfield, $table, $column ) = @{$entry};
                 next ENTRY unless exists $tables{$table};
@@ -2604,9 +2592,9 @@ sub TransformMarcToKoha {
             # deal with subfields
           MARCSUBFIELD: foreach my $sf ( $field->subfields() ) {
                 my $code = $sf->[0];
-                next MARCSUBFIELD unless exists $inverted_field_map->{$frameworkcode}->{$tag}->{sfs}->{$code};
+                next MARCSUBFIELD unless exists $inverted_field_map->{$tag}->{sfs}->{$code};
                 my $value = $sf->[1];
-              SFENTRY: foreach my $entry ( @{ $inverted_field_map->{$frameworkcode}->{$tag}->{sfs}->{$code} } ) {
+              SFENTRY: foreach my $entry ( @{ $inverted_field_map->{$tag}->{sfs}->{$code} } ) {
                     my ( $table, $column ) = @{$entry};
                     next SFENTRY unless exists $tables{$table};
                     my $key = _disambiguate( $table, $column );
@@ -2649,18 +2637,17 @@ sub TransformMarcToKoha {
 }
 
 sub _get_inverted_marc_field_map {
+    my ( $frameworkcode ) = @_;
     my $field_map = {};
-    my $relations = C4::Context->marcfromkohafield;
+    my $mss = GetMarcSubfieldStructure( $frameworkcode );
 
-    foreach my $frameworkcode ( keys %{$relations} ) {
-        foreach my $kohafield ( keys %{ $relations->{$frameworkcode} } ) {
-            next unless @{ $relations->{$frameworkcode}->{$kohafield} };    # not all columns are mapped to MARC tag & subfield
-            my $tag      = $relations->{$frameworkcode}->{$kohafield}->[0];
-            my $subfield = $relations->{$frameworkcode}->{$kohafield}->[1];
-            my ( $table, $column ) = split /[.]/, $kohafield, 2;
-            push @{ $field_map->{$frameworkcode}->{$tag}->{list} }, [ $subfield, $table, $column ];
-            push @{ $field_map->{$frameworkcode}->{$tag}->{sfs}->{$subfield} }, [ $table, $column ];
-        }
+    foreach my $kohafield ( keys %{ $mss } ) {
+        next unless exists $mss->{$kohafield};    # not all columns are mapped to MARC tag & subfield
+        my $tag      = $mss->{$kohafield}{tagfield};
+        my $subfield = $mss->{$kohafield}{tagsubfield};
+        my ( $table, $column ) = split /[.]/, $kohafield, 2;
+        push @{ $field_map->{$tag}->{list} }, [ $subfield, $table, $column ];
+        push @{ $field_map->{$tag}->{sfs}->{$subfield} }, [ $table, $column ];
     }
     return $field_map;
 }
@@ -2827,31 +2814,62 @@ sub TransformMarcToKohaOneField {
 
 =head2 ModZebra
 
-  ModZebra( $biblionumber, $op, $server );
+  ModZebra( $biblionumber, $op, $server, $record );
 
 $biblionumber is the biblionumber we want to index
 
-$op is specialUpdate or delete, and is used to know what we want to do
+$op is specialUpdate or recordDelete, and is used to know what we want to do
 
 $server is the server that we want to update
 
+$record is the update MARC record if it's available. If it's not supplied
+and is needed, it'll be loaded from the database.
+
 =cut
 
 sub ModZebra {
 ###Accepts a $server variable thus we can use it for biblios authorities or other zebra dbs
-    my ( $biblionumber, $op, $server ) = @_;
+    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 $dbh = C4::Context->dbh;
 
     # true ModZebra commented until indexdata fixes zebraDB crashes (it seems they occur on multiple updates
     # at the same time
     # replaced by a zebraqueue table, that is filled with ModZebra to run.
     # the table is emptied by rebuild_zebra.pl script (using the -z switch)
-
     my $check_sql = "SELECT COUNT(*) FROM zebraqueue
-                     WHERE server = ?
-                     AND   biblio_auth_number = ?
-                     AND   operation = ?
-                     AND   done = 0";
+    WHERE server = ?
+        AND   biblio_auth_number = ?
+        AND   operation = ?
+        AND   done = 0";
     my $check_sth = $dbh->prepare_cached($check_sql);
     $check_sth->execute( $server, $biblionumber, $op );
     my ($count) = $check_sth->fetchrow_array;
@@ -2866,17 +2884,19 @@ sub ModZebra {
 
 =head2 EmbedItemsInMarcBiblio
 
-    EmbedItemsInMarcBiblio($marc, $biblionumber, $itemnumbers);
+    EmbedItemsInMarcBiblio($marc, $biblionumber, $itemnumbers, $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 $itemnumbers is defined, only specified itemnumbers are embedded.
+
+If $opac is true, then opac-relevant suppressions are included.
 
 =cut
 
 sub EmbedItemsInMarcBiblio {
-    my ($marc, $biblionumber, $itemnumbers) = @_;
+    my ($marc, $biblionumber, $itemnumbers, $opac) = @_;
     if ( !$marc ) {
         carp 'EmbedItemsInMarcBiblio: No MARC record passed';
         return;
@@ -2893,10 +2913,24 @@ sub EmbedItemsInMarcBiblio {
     $sth->execute($biblionumber);
     my @item_fields;
     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
-    while (my ($itemnumber) = $sth->fetchrow_array) {
+    my @items;
+    my $opachiddenitems = $opac
+      && ( C4::Context->preference('OpacHiddenItems') !~ /^\s*$/ );
+    require C4::Items;
+    while ( my ($itemnumber) = $sth->fetchrow_array ) {
         next if @$itemnumbers and not grep { $_ == $itemnumber } @$itemnumbers;
-        require C4::Items;
-        my $item_marc = C4::Items::GetMarcItem($biblionumber, $itemnumber);
+        my $i = $opachiddenitems ? C4::Items::GetItem($itemnumber) : undef;
+        push @items, { itemnumber => $itemnumber, item => $i };
+    }
+    my @hiddenitems =
+      $opachiddenitems
+      ? C4::Items::GetHiddenItemnumbers( map { $_->{item} } @items )
+      : ();
+    # 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);
@@ -2917,56 +2951,20 @@ the MARC XML.
 sub _koha_marc_update_bib_ids {
     my ( $record, $frameworkcode, $biblionumber, $biblioitemnumber ) = @_;
 
-    # we must add bibnum and bibitemnum in MARC::Record...
-    # we build the new field with biblionumber and biblioitemnumber
-    # we drop the original field
-    # we add the new builded field.
     my ( $biblio_tag,     $biblio_subfield )     = GetMarcFromKohaField( "biblio.biblionumber",          $frameworkcode );
     die qq{No biblionumber tag for framework "$frameworkcode"} unless $biblio_tag;
     my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.biblioitemnumber", $frameworkcode );
     die qq{No biblioitemnumber tag for framework "$frameworkcode"} unless $biblioitem_tag;
 
-    if ( $biblio_tag == $biblioitem_tag ) {
-
-        # biblionumber & biblioitemnumber are in the same field (can't be <10 as fields <10 have only 1 value)
-        my $new_field = MARC::Field->new(
-            $biblio_tag, '', '',
-            "$biblio_subfield"     => $biblionumber,
-            "$biblioitem_subfield" => $biblioitemnumber
-        );
-
-        # drop old field and create new one...
-        my $old_field = $record->field($biblio_tag);
-        $record->delete_field($old_field) if $old_field;
-        $record->insert_fields_ordered($new_field);
+    if ( $biblio_tag < 10 ) {
+        C4::Biblio::UpsertMarcControlField( $record, $biblio_tag, $biblionumber );
     } else {
-
-        # biblionumber & biblioitemnumber are in different fields
-
-        # deal with biblionumber
-        my ( $new_field, $old_field );
-        if ( $biblio_tag < 10 ) {
-            $new_field = MARC::Field->new( $biblio_tag, $biblionumber );
-        } else {
-            $new_field = MARC::Field->new( $biblio_tag, '', '', "$biblio_subfield" => $biblionumber );
-        }
-
-        # drop old field and create new one...
-        $old_field = $record->field($biblio_tag);
-        $record->delete_field($old_field) if $old_field;
-        $record->insert_fields_ordered($new_field);
-
-        # deal with biblioitemnumber
-        if ( $biblioitem_tag < 10 ) {
-            $new_field = MARC::Field->new( $biblioitem_tag, $biblioitemnumber, );
-        } else {
-            $new_field = MARC::Field->new( $biblioitem_tag, '', '', "$biblioitem_subfield" => $biblioitemnumber, );
-        }
-
-        # drop old field and create new one...
-        $old_field = $record->field($biblioitem_tag);
-        $record->delete_field($old_field) if $old_field;
-        $record->insert_fields_ordered($new_field);
+        C4::Biblio::UpsertMarcSubfield($record, $biblio_tag, $biblio_subfield, $biblionumber);
+    }
+    if ( $biblioitem_tag < 10 ) {
+        C4::Biblio::UpsertMarcControlField( $record, $biblioitem_tag, $biblioitemnumber );
+    } else {
+        C4::Biblio::UpsertMarcSubfield($record, $biblioitem_tag, $biblioitem_subfield, $biblioitemnumber);
     }
 }
 
@@ -3098,9 +3096,6 @@ sub _koha_modify_biblio {
 
   my ($biblioitemnumber,$error) = _koha_modify_biblioitem_nonmarc( $dbh, $biblioitem );
 
-Updates biblioitems row except for marc and marcxml, which should be changed
-via ModBiblioMarc
-
 =cut
 
 sub _koha_modify_biblioitem_nonmarc {
@@ -3196,7 +3191,6 @@ sub _koha_add_biblioitem {
         size            = ?,
         place           = ?,
         lccn            = ?,
-        marc            = ?,
         url             = ?,
         cn_source       = ?,
         cn_class        = ?,
@@ -3214,7 +3208,7 @@ sub _koha_add_biblioitem {
         $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->{'marc'},             $biblioitem->{'url'},                   $biblioitem->{'biblioitems.cn_source'},
+        $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'}
     );
@@ -3249,6 +3243,8 @@ sub _koha_delete_biblio {
     my $sth = $dbh->prepare("SELECT * FROM biblio WHERE biblionumber=?");
     $sth->execute($biblionumber);
 
+    # FIXME There is a transaction in _koha_delete_biblio_metadata
+    # But actually all the following should be done inside a single transaction
     if ( my $data = $sth->fetchrow_hashref ) {
 
         # save the record in deletedbiblio
@@ -3266,6 +3262,8 @@ sub _koha_delete_biblio {
         $bkup_sth->execute(@bind);
         $bkup_sth->finish;
 
+        _koha_delete_biblio_metadata( $biblionumber );
+
         # delete the biblio
         my $sth2 = $dbh->prepare("DELETE FROM biblio WHERE biblionumber=?");
         $sth2->execute($biblionumber);
@@ -3327,13 +3325,38 @@ sub _koha_delete_biblioitems {
     return;
 }
 
+=head2 _koha_delete_biblio_metadata
+
+  $error = _koha_delete_biblio_metadata($biblionumber);
+
+C<$biblionumber> - the biblionumber of the biblio metadata to be deleted
+
+=cut
+
+sub _koha_delete_biblio_metadata {
+    my ($biblionumber) = @_;
+
+    my $dbh    = C4::Context->dbh;
+    my $schema = Koha::Database->new->schema;
+    $schema->txn_do(
+        sub {
+            $dbh->do( q|
+                INSERT INTO deletedbiblio_metadata (biblionumber, format, marcflavour, metadata)
+                SELECT biblionumber, format, marcflavour, metadata FROM biblio_metadata WHERE biblionumber=?
+            |,  undef, $biblionumber );
+            $dbh->do( q|DELETE FROM biblio_metadata WHERE biblionumber=?|,
+                undef, $biblionumber );
+        }
+    );
+}
+
 =head1 UNEXPORTED FUNCTIONS
 
 =head2 ModBiblioMarc
 
   &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
 
-Add MARC data for a biblio to koha 
+Add MARC XML data for a biblio to koha
 
 Function exported, but should NOT be used, unless you really know what you're doing
 
@@ -3341,7 +3364,7 @@ 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 marc field
+    # the marcxml field
     my ( $record, $biblionumber, $frameworkcode ) = @_;
     if ( !$record ) {
         carp 'ModBiblioMarc passed an undefined record';
@@ -3388,81 +3411,27 @@ sub ModBiblioMarc {
       $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
     }
 
-    $sth = $dbh->prepare("UPDATE biblioitems SET marc=?,marcxml=? WHERE biblionumber=?");
-    $sth->execute( $record->as_usmarc(), $record->as_xml_record($encoding), $biblionumber );
-    $sth->finish;
-    ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
-    return $biblionumber;
-}
-
-=head2 get_biblio_authorised_values
-
-find the types and values for all authorised values assigned to this biblio.
-
-parameters:
-    biblionumber
-    MARC::Record of the bib
-
-returns: a hashref mapping the authorised value to the value set for this biblionumber
-
-  $authorised_values = {
-                       'Scent'     => 'flowery',
-                       'Audience'  => 'Young Adult',
-                       'itemtypes' => 'SER',
-                        };
-
-Notes: forlibrarian should probably be passed in, and called something different.
-
-=cut
-
-sub get_biblio_authorised_values {
-    my $biblionumber = shift;
-    my $record       = shift;
-
-    my $forlibrarian  = 1;                                 # are we in staff or opac?
-    my $frameworkcode = GetFrameworkCode($biblionumber);
-
-    my $authorised_values;
-
-    my $tagslib = GetMarcStructure( $forlibrarian, $frameworkcode )
-      or return $authorised_values;
-
-    # assume that these entries in the authorised_value table are bibliolevel.
-    # ones that start with 'item%' are item level.
-    my $query = q(SELECT distinct authorised_value, kohafield
-                    FROM marc_subfield_structure
-                    WHERE authorised_value !=''
-                      AND (kohafield like 'biblio%'
-                       OR  kohafield like '') );
-    my $bibliolevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
-
-    foreach my $tag ( keys(%$tagslib) ) {
-        foreach my $subfield ( keys( %{ $tagslib->{$tag} } ) ) {
-
-            # warn "checking $subfield. type is: " . ref $tagslib->{ $tag }{ $subfield };
-            if ( 'HASH' eq ref $tagslib->{$tag}{$subfield} ) {
-                if ( defined $tagslib->{$tag}{$subfield}{'authorised_value'} && exists $bibliolevel_authorised_values->{ $tagslib->{$tag}{$subfield}{'authorised_value'} } ) {
-                    if ( defined $record->field($tag) ) {
-                        my $this_subfield_value = $record->field($tag)->subfield($subfield);
-                        if ( defined $this_subfield_value ) {
-                            $authorised_values->{ $tagslib->{$tag}{$subfield}{'authorised_value'} } = $this_subfield_value;
-                        }
-                    }
-                }
-            }
-        }
+    my $metadata = {
+        biblionumber => $biblionumber,
+        format       => 'marcxml',
+        marcflavour  => C4::Context->preference('marcflavour'),
+    };
+    # FIXME To replace with ->find_or_create?
+    if ( my $m_rs = Koha::Biblio::Metadatas->find($metadata) ) {
+        $m_rs->metadata( $record->as_xml_record($encoding) );
+        $m_rs->store;
+    } else {
+        my $m_rs = Koha::Biblio::Metadata->new($metadata);
+        $m_rs->metadata( $record->as_xml_record($encoding) );
+        $m_rs->store;
     }
-
-    # warn ( Data::Dumper->Dump( [ $authorised_values ], [ 'authorised_values' ] ) );
-    return $authorised_values;
+    ModZebra( $biblionumber, "specialUpdate", "biblioserver", $record );
+    return $biblionumber;
 }
 
 =head2 CountBiblioInOrders
 
-=over 4
-$count = &CountBiblioInOrders( $biblionumber);
-
-=back
+    $count = &CountBiblioInOrders( $biblionumber);
 
 This function return count of biblios in orders with $biblionumber 
 
@@ -3480,52 +3449,6 @@ sub CountBiblioInOrders {
     return ($count);
 }
 
-=head2 GetSubscriptionsId
-
-=over 4
-$subscriptions = &GetSubscriptionsId($biblionumber);
-
-=back
-
-This function return an array of subscriptionid with $biblionumber
-
-=cut
-
-sub GetSubscriptionsId {
- my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $query          = "SELECT subscriptionid
-          FROM  subscription
-          WHERE biblionumber=?";
-    my $sth = $dbh->prepare($query);
-    $sth->execute($biblionumber);
-    my @subscriptions = $sth->fetchrow_array;
-    return (@subscriptions);
-}
-
-=head2 GetHolds
-
-=over 4
-$holds = &GetHolds($biblionumber);
-
-=back
-
-This function return the count of holds with $biblionumber
-
-=cut
-
-sub GetHolds {
- my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $query          = "SELECT count(*)
-          FROM  reserves
-          WHERE biblionumber=?";
-    my $sth = $dbh->prepare($query);
-    $sth->execute($biblionumber);
-    my $holds = $sth->fetchrow;
-    return ($holds);
-}
-
 =head2 prepare_host_field
 
 $marcfield = prepare_host_field( $hostbiblioitem, $marcflavour );
@@ -3536,7 +3459,7 @@ Generate the host item entry for an analytic child entry
 sub prepare_host_field {
     my ( $hostbiblio, $marcflavour ) = @_;
     $marcflavour ||= C4::Context->preference('marcflavour');
-    my $host = GetMarcBiblio($hostbiblio);
+    my $host = GetMarcBiblio({ biblionumber => $hostbiblio });
     # unfortunately as_string does not 'do the right thing'
     # if field returns undef
     my %sfd;
@@ -3618,7 +3541,7 @@ sub prepare_host_field {
         if ( $field = $host->field('205') ) {
             my $s = $field->as_string();
             if ($s) {
-                $sfd{a} = $s;
+                $sfd{e} = $s;
             }
         }
         #URL
@@ -3674,16 +3597,27 @@ sub UpdateTotalIssues {
     my ($biblionumber, $increase, $value) = @_;
     my $totalissues;
 
-    my $data = GetBiblioData($biblionumber);
+    my $record = GetMarcBiblio({ biblionumber => $biblionumber });
+    unless ($record) {
+        carp "UpdateTotalIssues could not get biblio record";
+        return;
+    }
+    my $biblio = Koha::Biblios->find( $biblionumber );
+    unless ($biblio) {
+        carp "UpdateTotalIssues could not get datas of biblio";
+        return;
+    }
+    my $biblioitem = $biblio->biblioitem;
+    my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $biblio->frameworkcode);
+    unless ($totalissuestag) {
+        return 1; # There is nothing to do
+    }
 
     if (defined $value) {
         $totalissues = $value;
     } else {
-        $totalissues = $data->{'totalissues'} + $increase;
+        $totalissues = $biblioitem->totalissues + $increase;
     }
-     my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $data->{'frameworkcode'});
-
-     my $record = GetMarcBiblio($biblionumber);
 
      my $field = $record->field($totalissuestag);
      if (defined $field) {
@@ -3694,8 +3628,7 @@ sub UpdateTotalIssues {
          $record->insert_grouped_field($field);
      }
 
-     ModBiblio($record, $biblionumber, $data->{'frameworkcode'});
-     return;
+     return ModBiblio($record, $biblionumber, $biblio->frameworkcode);
 }
 
 =head2 RemoveAllNsb