Bug 19940: Koha::Biblio - Remove GetBiblioItemInfosOf
[koha.git] / C4 / Biblio.pm
index 3ff948b..89bb4f8 100644 (file)
@@ -23,6 +23,7 @@ use Modern::Perl;
 use Carp;
 
 use Encode qw( decode is_utf8 );
+use List::MoreUtils qw( uniq );
 use MARC::Record;
 use MARC::File::USMARC;
 use MARC::File::XML;
@@ -43,6 +44,8 @@ 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;
 
@@ -62,19 +65,11 @@ BEGIN {
 
     # to get something
     push @EXPORT, qw(
-      GetBiblio
       GetBiblioData
       GetMarcBiblio
       GetBiblioItemData
-      GetBiblioItemInfosOf
-      GetBiblioItemByBiblioNumber
-      GetBiblioFromItemNumber
-      GetBiblionumberFromItemnumber
 
       &GetRecordValue
-      &GetFieldMapping
-      &SetFieldMapping
-      &DeleteFieldMapping
 
       &GetISBDView
 
@@ -105,7 +100,6 @@ BEGIN {
 
       &CountItemsIssued
       &CountBiblioInOrders
-      &GetSubscriptionsId
     );
 
     # To modify something
@@ -163,7 +157,7 @@ Biblio.pm contains functions for managing storage and editing of bibliographic d
 
 =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.
 
@@ -298,7 +292,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 );
     }
 
@@ -409,10 +403,10 @@ sub DelBiblio {
     }
 
     # We delete any existing holds
-    require C4::Reserves;
-    my $reserves = C4::Reserves::GetReservesFromBiblionumber({ biblionumber => $biblionumber });
-    foreach my $res ( @$reserves ) {
-        C4::Reserves::CancelReserve({ reserve_id => $res->{'reserve_id'} });
+    my $biblio = Koha::Biblios->find( $biblionumber );
+    my $holds = $biblio->holds;
+    while ( my $hold = $holds->next ) {
+        $hold->cancel;
     }
 
     # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
@@ -431,6 +425,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
@@ -572,6 +567,11 @@ sub LinkBibHeadingsToAuthorities {
 # of change to a core API just before the 3.0 release.
 
                     if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
+                        my $userenv = C4::Context->userenv;
+                        my $library;
+                        if ( $userenv && $userenv->{'branch'} ) {
+                            $library = Koha::Libraries->find( $userenv->{'branch'} );
+                        }
                         $marcrecordauth->insert_fields_ordered(
                             MARC::Field->new(
                                 '667', '', '',
@@ -586,7 +586,7 @@ sub LinkBibHeadingsToAuthorities {
                         $cite =~ s/[\s\,]*$//;
                         $cite =
                             "Work cat.: ("
-                          . C4::Context->preference('MARCOrgCode') . ")"
+                          . ( $library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') ) . ")"
                           . $bib->subfield( '999', 'c' ) . ": "
                           . $cite;
                         $marcrecordauth->insert_fields_ordered(
@@ -685,66 +685,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);
@@ -780,7 +720,7 @@ sub GetBiblioData {
     return ($data);
 }    # sub GetBiblioData
 
-=head2 &GetBiblioItemData
+=head2 GetBiblioItemData
 
   $itemdata = &GetBiblioItemData($biblioitemnumber);
 
@@ -791,7 +731,6 @@ that C<biblioitems.notes> is given as C<$itemdata-E<gt>{bnotes}>.
 
 =cut
 
-#'
 sub GetBiblioItemData {
     my ($biblioitemnumber) = @_;
     my $dbh                = C4::Context->dbh;
@@ -809,83 +748,6 @@ sub GetBiblioItemData {
     return ($data);
 }    # sub &GetBiblioItemData
 
-=head2 GetBiblioItemByBiblioNumber
-
-NOTE : This function has been copy/paste from C4/Biblio.pm from head before zebra integration.
-
-=cut
-
-sub GetBiblioItemByBiblioNumber {
-    my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare("Select * FROM biblioitems WHERE biblionumber = ?");
-    my $count          = 0;
-    my @results;
-
-    $sth->execute($biblionumber);
-
-    while ( my $data = $sth->fetchrow_hashref ) {
-        push @results, $data;
-    }
-
-    $sth->finish;
-    return @results;
-}
-
-=head2 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({
@@ -1022,46 +884,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);
-
-=cut
-
-sub GetBiblioItemInfosOf {
-    my @biblioitemnumbers = @_;
-
-    my $biblioitemnumber_values = @biblioitemnumbers ? join( ',', @biblioitemnumbers ) : "''";
-
-    my $query = "
-        SELECT biblioitemnumber,
-            publicationyear,
-            itemtype
-        FROM biblioitems
-        WHERE biblioitemnumber IN ($biblioitemnumber_values)
-    ";
-    return get_infos_of( $query, 'biblioitemnumber' );
-}
-
 =head1 FUNCTIONS FOR HANDLING MARC MANAGEMENT
 
 =head2 IsMarcStructureInternal
@@ -1184,7 +1006,7 @@ in tab 0-9. (used field)
 
   my $results = GetUsedMarcStructure($frameworkcode);
 
-C<$results> is a ref to an array which each case containts a ref
+C<$results> is a ref to an array which each case contains a ref
 to a hash which each keys is the columns from marc_subfield_structure
 
 C<$frameworkcode> is the framework code. 
@@ -1220,62 +1042,92 @@ sub GetMarcSubfieldStructure {
     return $cached if $cached;
 
     my $dbh = C4::Context->dbh;
-    my $subfield_structure = $dbh->selectall_hashref( q|
+    # We moved to selectall_arrayref since selectall_hashref does not
+    # keep duplicate mappings on kohafield (like place in 260 vs 264)
+    my $subfield_aref = $dbh->selectall_arrayref( q|
         SELECT *
         FROM marc_subfield_structure
         WHERE frameworkcode = ?
         AND kohafield > ''
-    |, 'kohafield', {}, $frameworkcode );
-
+        ORDER BY frameworkcode,tagfield,tagsubfield
+    |, { Slice => {} }, $frameworkcode );
+    # Now map the output to a hash structure
+    my $subfield_structure = {};
+    foreach my $row ( @$subfield_aref ) {
+        push @{ $subfield_structure->{ $row->{kohafield} }}, $row;
+    }
     $cache->set_in_cache( $cache_key, $subfield_structure );
     return $subfield_structure;
 }
 
 =head2 GetMarcFromKohaField
 
-  ($MARCfield,$MARCsubfield)=GetMarcFromKohaField($kohafield,$frameworkcode);
+    ( $field,$subfield ) = GetMarcFromKohaField( $kohafield );
+    @fields = GetMarcFromKohaField( $kohafield );
+    $field = GetMarcFromKohaField( $kohafield );
 
-Returns the MARC fields & subfields mapped to the koha field 
-for the given frameworkcode or default framework if $frameworkcode is missing
+    Returns the MARC fields & subfields mapped to $kohafield.
+    Since the Default framework is considered as authoritative for such
+    mappings, the former frameworkcode parameter is obsoleted.
+
+    In list context all mappings are returned; there can be multiple
+    mappings. Note that in the above example you could miss a second
+    mappings in the first call.
+    In scalar context only the field tag of the first mapping is returned.
 
 =cut
 
 sub GetMarcFromKohaField {
-    my ( $kohafield, $frameworkcode ) = @_;
-    return (0, undef) unless $kohafield;
-    my $mss = GetMarcSubfieldStructure( $frameworkcode );
-    return ( $mss->{$kohafield}{tagfield}, $mss->{$kohafield}{tagsubfield} );
+    my ( $kohafield ) = @_;
+    return unless $kohafield;
+    # The next call uses the Default framework since it is AUTHORITATIVE
+    # for all Koha to MARC mappings.
+    my $mss = GetMarcSubfieldStructure( '' ); # Do not change framework
+    my @retval;
+    foreach( @{ $mss->{$kohafield} } ) {
+        push @retval, $_->{tagfield}, $_->{tagsubfield};
+    }
+    return wantarray ? @retval : ( @retval ? $retval[0] : undef );
 }
 
 =head2 GetMarcSubfieldStructureFromKohaField
 
-    my $subfield_structure = &GetMarcSubfieldStructureFromKohaField($kohafield, $frameworkcode);
-
-Returns a hashref where keys are marc_subfield_structure column names for the
-row where kohafield=$kohafield for the given framework code.
+    my $str = GetMarcSubfieldStructureFromKohaField( $kohafield );
 
-$frameworkcode is optional. If not given, then the default framework is used.
+    Returns marc subfield structure information for $kohafield.
+    The Default framework is used, since it is authoritative for kohafield
+    mappings.
+    In list context returns a list of all hashrefs, since there may be
+    multiple mappings. In scalar context the first hashref is returned.
 
 =cut
 
 sub GetMarcSubfieldStructureFromKohaField {
-    my ( $kohafield, $frameworkcode ) = @_;
+    my ( $kohafield ) = @_;
 
     return unless $kohafield;
 
-    my $mss = GetMarcSubfieldStructure( $frameworkcode );
-    return exists $mss->{$kohafield}
-        ? $mss->{$kohafield}
-        : undef;
+    # The next call uses the Default framework since it is AUTHORITATIVE
+    # for all Koha to MARC mappings.
+    my $mss = GetMarcSubfieldStructure(''); # Do not change framework
+    return unless $mss->{$kohafield};
+    return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
 }
 
 =head2 GetMarcBiblio
 
-  my $record = GetMarcBiblio($biblionumber, [$embeditems], [$opac]);
+  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>
@@ -1296,9 +1148,16 @@ OpacHiddenItems to be applied.
 =cut
 
 sub GetMarcBiblio {
-    my $biblionumber = shift;
-    my $embeditems   = shift || 0;
-    my $opac         = 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';
@@ -1484,9 +1343,9 @@ sub GetCOinSBiblio {
             $isbn      = $record->subfield( '773', 'z' ) || '';
             $issn      = $record->subfield( '773', 'x' ) || '';
             if ($mtx eq 'journal') {
-                $title    .= "&amp;rft.title=" . (($record->subfield( '773', 't' ) || $record->subfield( '773', 'a')));
+                $title    .= "&amp;rft.title=" . ( $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{} );
             } else {
-                $title    .= "&amp;rft.btitle=" . (($record->subfield( '773', 't' ) || $record->subfield( '773', 'a')) || '');
+                $title    .= "&amp;rft.btitle=" . ( $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{} );
             }
             foreach my $rel ($record->subfield( '773', 'g' )) {
                 if ($pages) {
@@ -1556,6 +1415,7 @@ sub GetMarcPrice {
 =head2 MungeMarcPrice
 
 Return the best guess at what the actual price is from a price field.
+
 =cut
 
 sub MungeMarcPrice {
@@ -1688,7 +1548,8 @@ sub GetAuthorisedValueDesc {
 
         #---- itemtypes
         if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
-            return getitemtypeinfo($value)->{translated_description};
+            my $itemtype = Koha::ItemTypes->find( $value );
+            return $itemtype ? $itemtype->translated_description : q||;
         }
 
         #---- "true" authorized value
@@ -1802,7 +1663,7 @@ sub GetMarcISSN {
 
     Get all notes from the MARC record and returns them in an array.
     The notes are stored in different fields depending on MARC flavour.
-    MARC21 field 555 gets special attention for the $u subfields.
+    MARC21 5XX $u subfields receive special attention as they are URIs.
 
 =cut
 
@@ -1820,12 +1681,16 @@ sub GetMarcNotes {
     foreach my $field ( $record->field($scope) ) {
         my $tag = $field->tag();
         next if $blacklist{ $tag };
-        if( $marcflavour ne 'UNIMARC' && $tag =~ /555/ ) {
-            # Field 555$u contains URLs
-            # We first push the regular subfields and all $u's separately
-            # Leave further actions to the template
-            push @marcnotes, { marcnote => $field->as_string('abcd') };
+        if( $marcflavour ne 'UNIMARC' && $field->subfield('u') ) {
+            # Field 5XX$u always contains URI
+            # Examples: 505u, 506u, 510u, 514u, 520u, 530u, 538u, 540u, 542u, 552u, 555u, 561u, 563u, 583u
+            # We first push the other subfields, then all $u's separately
+            # Leave further actions to the template (see e.g. opac-detail)
+            my $othersub =
+                join '', ( 'a' .. 't', 'v' .. 'z', '0' .. '9' ); # excl 'u'
+            push @marcnotes, { marcnote => $field->as_string($othersub) };
             foreach my $sub ( $field->subfield('u') ) {
+                $sub =~ s/^\s+|\s+$//g; # trim
                 push @marcnotes, { marcnote => $sub };
             }
         } else {
@@ -2047,6 +1912,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');
@@ -2260,42 +2126,51 @@ sub GetFrameworkCode {
 
 =head2 TransformKohaToMarc
 
-    $record = TransformKohaToMarc( $hash )
+    $record = TransformKohaToMarc( $hash [, $params ]  )
 
-This function builds partial MARC::Record from a hash
-Hash entries can be from biblio or biblioitems.
+This function builds a (partial) MARC::Record from a hash.
+Hash entries can be from biblio, biblioitems or items.
+The params hash includes the parameter no_split used in C4::Items.
 
 This function is called in acquisition module, to create a basic catalogue
-entry from user entry
+entry from user entry.
 
 =cut
 
 
 sub TransformKohaToMarc {
-    my $hash = shift;
+    my ( $hash, $params ) = @_;
     my $record = MARC::Record->new();
     SetMarcUnicodeFlag( $record, C4::Context->preference("marcflavour") );
-    # FIXME Do not we want to get the marc subfield structure for the biblio framework?
-    my $mss = GetMarcSubfieldStructure();
+
+    # In the next call we use the Default framework, since it is considered
+    # authoritative for Koha to Marc mappings.
+    my $mss = GetMarcSubfieldStructure( '' ); # do not change framework
     my $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) ) {
-            next if $value eq '';
-            $tag_hr->{$tagfield} //= [];
-            push @{$tag_hr->{$tagfield}}, [($tagsubfield, $value)];
+        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);
+            foreach my $value ( @values ) {
+                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)
-        );
+        # Special care for control fields: remove the subfield indication @
+        # and do not insert indicators.
+        my @ind = $tag < 10 ? () : ( " ", " " );
+        @sfl = grep { $_ ne '@' } @sfl if $tag < 10;
+        $record->insert_fields_ordered( MARC::Field->new($tag, @ind, @sfl) );
     }
     return $record;
 }
@@ -2313,7 +2188,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;
@@ -2537,7 +2412,7 @@ sub _default_ind_to_space {
 =head2 TransformHtmlToMarc
 
     L<$record> = TransformHtmlToMarc(L<$cgi>)
-    L<$cgi> is the CGI object which containts the values for subfields
+    L<$cgi> is the CGI object which contains the values for subfields
     {
         'tag_010_indicator1_531951' ,
         'tag_010_indicator2_531951' ,
@@ -2655,119 +2530,44 @@ sub TransformHtmlToMarc {
 
 =head2 TransformMarcToKoha
 
-  $result = TransformMarcToKoha( $record, $frameworkcode )
+    $result = TransformMarcToKoha( $record, undef, $limit )
 
 Extract data from a MARC bib record into a hashref representing
-Koha biblio, biblioitems, and items fields. 
+Koha biblio, biblioitems, and items fields.
 
 If passed an undefined record will log the error and return an empty
-hash_ref
+hash_ref.
 
 =cut
 
 sub TransformMarcToKoha {
     my ( $record, $frameworkcode, $limit_table ) = @_;
+    # FIXME  Parameter $frameworkcode is obsolete and will be removed
+    $limit_table //= q{};
 
     my $result = {};
     if (!defined $record) {
         carp('TransformMarcToKoha called with undefined record');
         return $result;
     }
-    $limit_table = $limit_table || 0;
-    $frameworkcode = '' unless defined $frameworkcode;
 
-    my $inverted_field_map = _get_inverted_marc_field_map($frameworkcode);
-
-    my %tables = ();
-    if ( defined $limit_table && $limit_table eq 'items' ) {
-        $tables{'items'} = 1;
-    } else {
-        $tables{'items'}       = 1;
-        $tables{'biblio'}      = 1;
-        $tables{'biblioitems'} = 1;
+    my %tables = ( biblio => 1, biblioitems => 1, items => 1 );
+    if( $limit_table eq 'items' ) {
+        %tables = ( items => 1 );
     }
 
-    # traverse through record
-  MARCFIELD: foreach my $field ( $record->fields() ) {
-        my $tag = $field->tag();
-        next MARCFIELD unless exists $inverted_field_map->{$tag};
-        if ( $field->is_control_field() ) {
-            my $kohafields = $inverted_field_map->{$tag}->{list};
-          ENTRY: foreach my $entry ( @{$kohafields} ) {
-                my ( $subfield, $table, $column ) = @{$entry};
-                next ENTRY unless exists $tables{$table};
-                my $key = _disambiguate( $table, $column );
-                if ( $result->{$key} ) {
-                    unless ( ( $key eq "biblionumber" or $key eq "biblioitemnumber" ) and ( $field->data() eq "" ) ) {
-                        $result->{$key} .= " | " . $field->data();
-                    }
-                } else {
-                    $result->{$key} = $field->data();
-                }
-            }
-        } else {
-
-            # deal with subfields
-          MARCSUBFIELD: foreach my $sf ( $field->subfields() ) {
-                my $code = $sf->[0];
-                next MARCSUBFIELD unless exists $inverted_field_map->{$tag}->{sfs}->{$code};
-                my $value = $sf->[1];
-              SFENTRY: foreach my $entry ( @{ $inverted_field_map->{$tag}->{sfs}->{$code} } ) {
-                    my ( $table, $column ) = @{$entry};
-                    next SFENTRY unless exists $tables{$table};
-                    my $key = _disambiguate( $table, $column );
-                    if ( $result->{$key} ) {
-                        unless ( ( $key eq "biblionumber" or $key eq "biblioitemnumber" ) and ( $value eq "" ) ) {
-                            $result->{$key} .= " | " . $value;
-                        }
-                    } else {
-                        $result->{$key} = $value;
-                    }
-                }
-            }
-        }
-    }
-
-    # modify copyrightdate to keep only the 1st year found
-    if ( exists $result->{'copyrightdate'} ) {
-        my $temp = $result->{'copyrightdate'};
-        $temp =~ m/c(\d\d\d\d)/;
-        if ( $temp =~ m/c(\d\d\d\d)/ and $1 > 0 ) {    # search cYYYY first
-            $result->{'copyrightdate'} = $1;
-        } else {                                       # if no cYYYY, get the 1st date.
-            $temp =~ m/(\d\d\d\d)/;
-            $result->{'copyrightdate'} = $1;
-        }
-    }
-
-    # modify publicationyear to keep only the 1st year found
-    if ( exists $result->{'publicationyear'} ) {
-        my $temp = $result->{'publicationyear'};
-        if ( $temp =~ m/c(\d\d\d\d)/ and $1 > 0 ) {    # search cYYYY first
-            $result->{'publicationyear'} = $1;
-        } else {                                       # if no cYYYY, get the 1st date.
-            $temp =~ m/(\d\d\d\d)/;
-            $result->{'publicationyear'} = $1;
-        }
-    }
-
-    return $result;
-}
-
-sub _get_inverted_marc_field_map {
-    my ( $frameworkcode ) = @_;
-    my $field_map = {};
-    my $mss = GetMarcSubfieldStructure( $frameworkcode );
-
+    # The next call acknowledges Default as the authoritative framework
+    # for Koha to MARC mappings.
+    my $mss = GetMarcSubfieldStructure(''); # Do not change framework
     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 ];
+        next unless $tables{$table};
+        my $val = TransformMarcToKohaOneField( $kohafield, $record );
+        next if !defined $val;
+        my $key = _disambiguate( $table, $column );
+        $result->{$key} = $val;
     }
-    return $field_map;
+    return $result;
 }
 
 =head2 _disambiguate
@@ -2799,15 +2599,6 @@ more.
 
 =cut
 
-sub CountItemsIssued {
-    my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare('SELECT COUNT(*) as issuedCount FROM items, issues WHERE items.itemnumber = issues.itemnumber AND items.biblionumber = ?');
-    $sth->execute($biblionumber);
-    my $row = $sth->fetchrow_hashref();
-    return $row->{'issuedCount'};
-}
-
 sub _disambiguate {
     my ( $table, $column ) = @_;
     if ( $column eq "cn_sort" or $column eq "cn_source" ) {
@@ -2818,117 +2609,82 @@ sub _disambiguate {
 
 }
 
-=head2 get_koha_field_from_marc
+=head2 TransformMarcToKohaOneField
 
-  $result->{_disambiguate($table, $field)} = 
-     get_koha_field_from_marc($table,$field,$record,$frameworkcode);
+    $val = TransformMarcToKohaOneField( 'biblio.title', $marc );
 
-Internal function to map data from the MARC record to a specific non-MARC field.
-FIXME: this is meant to replace TransformMarcToKohaOneField after more testing.
+    Note: The authoritative Default framework is used implicitly.
 
 =cut
 
-sub get_koha_field_from_marc {
-    my ( $koha_table, $koha_column, $record, $frameworkcode ) = @_;
-    my ( $tagfield, $subfield ) = GetMarcFromKohaField( $koha_table . '.' . $koha_column, $frameworkcode );
-    my $kohafield;
-    foreach my $field ( $record->field($tagfield) ) {
-        if ( $field->tag() < 10 ) {
-            if ($kohafield) {
-                $kohafield .= " | " . $field->data();
+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 {
-                $kohafield = $field->data();
-            }
-        } else {
-            if ( $field->subfields ) {
-                my @subfields = $field->subfields();
-                foreach my $subfieldcount ( 0 .. $#subfields ) {
-                    if ( $subfields[$subfieldcount][0] eq $subfield ) {
-                        if ($kohafield) {
-                            $kohafield .= " | " . $subfields[$subfieldcount][1];
-                        } else {
-                            $kohafield = $subfields[$subfieldcount][1];
-                        }
-                    }
-                }
+                push @rv, grep { $_ } $fld->subfield($sub);
             }
         }
     }
-    return $kohafield;
+    return unless @rv;
+    $retval = join ' | ', uniq(@rv);
+
+    # Additional polishing for individual kohafields
+    if( $kohafield =~ /copyrightdate|publicationyear/ ) {
+        $retval = _adjust_pubyear( $retval );
+    }
+
+    return $retval;
 }
 
-=head2 TransformMarcToKohaOneField
+=head2 _adjust_pubyear
 
-  $result = TransformMarcToKohaOneField( $kohatable, $kohafield, $record, $result, $frameworkcode )
+    Helper routine for TransformMarcToKohaOneField
 
 =cut
 
-sub TransformMarcToKohaOneField {
-
-    # FIXME ? if a field has a repeatable subfield that is used in old-db,
-    # only the 1st will be retrieved...
-    my ( $kohatable, $kohafield, $record, $result, $frameworkcode ) = @_;
-    my $res = "";
-    my ( $tagfield, $subfield ) = GetMarcFromKohaField( $kohatable . "." . $kohafield, $frameworkcode );
-    foreach my $field ( $record->field($tagfield) ) {
-        if ( $field->tag() < 10 ) {
-            if ( $result->{$kohafield} ) {
-                $result->{$kohafield} .= " | " . $field->data();
-            } else {
-                $result->{$kohafield} = $field->data();
-            }
-        } else {
-            if ( $field->subfields ) {
-                my @subfields = $field->subfields();
-                foreach my $subfieldcount ( 0 .. $#subfields ) {
-                    if ( $subfields[$subfieldcount][0] eq $subfield ) {
-                        if ( $result->{$kohafield} ) {
-                            $result->{$kohafield} .= " | " . $subfields[$subfieldcount][1];
-                        } else {
-                            $result->{$kohafield} = $subfields[$subfieldcount][1];
-                        }
-                    }
-                }
-            }
-        }
+sub _adjust_pubyear {
+    my $retval = shift;
+    # modify return value to keep only the 1st year found
+    if( $retval =~ m/c(\d\d\d\d)/ and $1 > 0 ) { # search cYYYY first
+        $retval = $1;
+    } elsif( $retval =~ m/(\d\d\d\d)/ && $1 > 0 ) {
+        $retval = $1;
+    } elsif( $retval =~ m/
+             (?<year>\d)[-]?[.Xx?]{3}
+            |(?<year>\d{2})[.Xx?]{2}
+            |(?<year>\d{3})[.Xx?]
+            |(?<year>\d)[-]{3}\?
+            |(?<year>\d\d)[-]{2}\?
+            |(?<year>\d{3})[-]\?
+    /xms ) { # the form 198-? occurred in Dutch ISBD rules
+        my $digits = $+{year};
+        $retval = $digits * ( 10 ** ( 4 - length($digits) ));
     }
-    return $result;
+    return $retval;
 }
 
+=head2 CountItemsIssued
 
-#"
+    my $count = CountItemsIssued( $biblionumber );
 
-#
-# 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 misc/cronjobs/zebraqueue_start.pl script
-# =head2 ModZebrafiles
-#
-# &ModZebrafiles( $dbh, $biblionumber, $record, $folder, $server );
-#
-# =cut
-#
-# sub ModZebrafiles {
-#
-#     my ( $dbh, $biblionumber, $record, $folder, $server ) = @_;
-#
-#     my $op;
-#     my $zebradir =
-#       C4::Context->zebraconfig($server)->{directory} . "/" . $folder . "/";
-#     unless ( opendir( DIR, "$zebradir" ) ) {
-#         warn "$zebradir not found";
-#         return;
-#     }
-#     closedir DIR;
-#     my $filename = $zebradir . $biblionumber;
-#
-#     if ($record) {
-#         open( OUTPUT, ">", $filename . ".xml" );
-#         print OUTPUT $record;
-#         close OUTPUT;
-#     }
-# }
+=cut
+
+sub CountItemsIssued {
+    my ($biblionumber) = @_;
+    my $dbh            = C4::Context->dbh;
+    my $sth            = $dbh->prepare('SELECT COUNT(*) as issuedCount FROM items, issues WHERE items.itemnumber = issues.itemnumber AND items.biblionumber = ?');
+    $sth->execute($biblionumber);
+    my $row = $sth->fetchrow_hashref();
+    return $row->{'issuedCount'};
+}
 
 =head2 ModZebra
 
@@ -2962,7 +2718,9 @@ sub ModZebra {
         );
         if ( $op eq 'specialUpdate' ) {
             unless ($record) {
-                $record = GetMarcBiblio($biblionumber, 1);
+                $record = GetMarcBiblio({
+                    biblionumber => $biblionumber,
+                    embed_items  => 1 });
             }
             my $records = [$record];
             $indexer->update_index_background( [$biblionumber], [$record] );
@@ -3359,6 +3117,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
@@ -3376,6 +3136,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);
@@ -3437,6 +3199,31 @@ 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
@@ -3503,9 +3290,12 @@ sub ModBiblioMarc {
         format       => 'marcxml',
         marcflavour  => C4::Context->preference('marcflavour'),
     };
+    $record->as_usmarc; # Bug 20126/10455 This triggers field length calculation
+
     # FIXME To replace with ->find_or_create?
     if ( my $m_rs = Koha::Biblio::Metadatas->find($metadata) ) {
         $m_rs->metadata( $record->as_xml_record($encoding) );
+        $m_rs->store;
     } else {
         my $m_rs = Koha::Biblio::Metadata->new($metadata);
         $m_rs->metadata( $record->as_xml_record($encoding) );
@@ -3535,26 +3325,6 @@ sub CountBiblioInOrders {
     return ($count);
 }
 
-=head2 GetSubscriptionsId
-
-    $subscriptions = &GetSubscriptionsId($biblionumber);
-
-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 prepare_host_field
 
 $marcfield = prepare_host_field( $hostbiblioitem, $marcflavour );
@@ -3565,7 +3335,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;
@@ -3703,17 +3473,18 @@ sub UpdateTotalIssues {
     my ($biblionumber, $increase, $value) = @_;
     my $totalissues;
 
-    my $record = GetMarcBiblio($biblionumber);
+    my $record = GetMarcBiblio({ biblionumber => $biblionumber });
     unless ($record) {
         carp "UpdateTotalIssues could not get biblio record";
         return;
     }
-    my $data = GetBiblioData($biblionumber);
-    unless ($data) {
+    my $biblio = Koha::Biblios->find( $biblionumber );
+    unless ($biblio) {
         carp "UpdateTotalIssues could not get datas of biblio";
         return;
     }
-    my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $data->{'frameworkcode'});
+    my $biblioitem = $biblio->biblioitem;
+    my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $biblio->frameworkcode);
     unless ($totalissuestag) {
         return 1; # There is nothing to do
     }
@@ -3721,7 +3492,7 @@ sub UpdateTotalIssues {
     if (defined $value) {
         $totalissues = $value;
     } else {
-        $totalissues = $data->{'totalissues'} + $increase;
+        $totalissues = $biblioitem->totalissues + $increase;
     }
 
      my $field = $record->field($totalissuestag);
@@ -3733,7 +3504,7 @@ sub UpdateTotalIssues {
          $record->insert_grouped_field($field);
      }
 
-     return ModBiblio($record, $biblionumber, $data->{'frameworkcode'});
+     return ModBiblio($record, $biblionumber, $biblio->frameworkcode);
 }
 
 =head2 RemoveAllNsb