Bug 15572: Follow-up to fix error on authority creation
[koha.git] / C4 / Biblio.pm
index d232fa6..ef263e4 100644 (file)
@@ -6,24 +6,24 @@ 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 Carp;
 
-# use utf8;
+use Encode qw( decode is_utf8 );
 use MARC::Record;
 use MARC::File::USMARC;
 use MARC::File::XML;
@@ -31,13 +31,16 @@ 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 Koha::Cache;
+use Koha::Authority::Types;
+
 use vars qw($VERSION @ISA @EXPORT);
 
 BEGIN {
@@ -54,14 +57,14 @@ BEGIN {
 
     # to get something
     push @EXPORT, qw(
-      &Get
-      &GetBiblio
-      &GetBiblioData
-      &GetBiblioItemData
-      &GetBiblioItemInfosOf
-      &GetBiblioItemByBiblioNumber
-      &GetBiblioFromItemNumber
-      &GetBiblionumberFromItemnumber
+      GetBiblio
+      GetBiblioData
+      GetMarcBiblio
+      GetBiblioItemData
+      GetBiblioItemInfosOf
+      GetBiblioItemByBiblioNumber
+      GetBiblioFromItemNumber
+      GetBiblionumberFromItemnumber
 
       &GetRecordValue
       &GetFieldMapping
@@ -75,7 +78,6 @@ BEGIN {
       &GetMarcISBN
       &GetMarcISSN
       &GetMarcSubjects
-      &GetMarcBiblio
       &GetMarcAuthors
       &GetMarcSeries
       &GetMarcHosts
@@ -124,8 +126,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
     );
@@ -133,24 +135,12 @@ BEGIN {
     # Others functions
     push @EXPORT, qw(
       &TransformMarcToKoha
-      &TransformHtmlToMarc2
       &TransformHtmlToMarc
       &TransformHtmlToXml
-      &GetNoZebraIndexes
       prepare_host_field
     );
 }
 
-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
@@ -253,6 +243,10 @@ sub AddBiblio {
     my $frameworkcode   = shift;
     my $options         = @_ ? shift : undef;
     my $defer_marc_save = 0;
+    if (!$record) {
+        carp('AddBiblio called with undefined record');
+        return;
+    }
     if ( defined $options and exists $options->{'defer_marc_save'} and $options->{'defer_marc_save'} ) {
         $defer_marc_save = 1;
     }
@@ -302,15 +296,20 @@ in the C<biblio> and C<biblioitems> tables, as well as
 which fields are used to store embedded item, biblioitem,
 and biblionumber data for indexing.
 
+Returns 1 on success 0 on failure
+
 =cut
 
 sub ModBiblio {
     my ( $record, $biblionumber, $frameworkcode ) = @_;
-    croak "No record" unless $record;
+    if (!$record) {
+        carp 'No record passed to ModBiblio';
+        return 0;
+    }
 
     if ( C4::Context->preference("CataloguingLog") ) {
         my $newrecord = GetMarcBiblio($biblionumber);
-        logaction( "CATALOGUING", "MODIFY", $biblionumber, "BEFORE=>" . $newrecord->as_formatted );
+        logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
     }
 
     # Cleaning up invalid fields must be done early or SetUTF8Flag is liable to
@@ -404,9 +403,9 @@ sub ModBiblioframework {
   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
 
@@ -437,24 +436,16 @@ sub DelBiblio {
 
     # We delete any existing holds
     require C4::Reserves;
-    my ($count, $reserves) = C4::Reserves::GetReservesFromBiblionumber($biblionumber);
+    my $reserves = C4::Reserves::GetReservesFromBiblionumber({ biblionumber => $biblionumber });
     foreach my $res ( @$reserves ) {
-        C4::Reserves::CancelReserve( $res->{'biblionumber'}, $res->{'itemnumber'}, $res->{'borrowernumber'} );
+        C4::Reserves::CancelReserve({ reserve_id => $res->{'reserve_id'} });
     }
 
     # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
     # for at least 2 reasons :
-    # - we need to read the biblio if NoZebra is set (to remove it from the indexes
     # - if something goes wrong, the biblio may be deleted from Koha but not from zebra
     #   and we would have no way to remove it (except manually in zebra, but I bet it would be very hard to handle the problem)
-    my $oldRecord;
-    if ( C4::Context->preference("NoZebra") ) {
-
-        # only NoZebra indexing needs to have
-        # the previous version of the record
-        $oldRecord = GetMarcBiblio($biblionumber);
-    }
-    ModZebra( $biblionumber, "recordDelete", "biblioserver", $oldRecord, undef );
+    ModZebra( $biblionumber, "recordDelete", "biblioserver" );
 
     # delete biblioitems and items from Koha tables and save in deletedbiblioitems,deleteditems
     $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
@@ -472,7 +463,7 @@ sub DelBiblio {
     # from being generated by _koha_delete_biblioitems
     $error = _koha_delete_biblio( $dbh, $biblionumber );
 
-    logaction( "CATALOGUING", "DELETE", $biblionumber, "" ) if C4::Context->preference("CataloguingLog");
+    logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
 
     return;
 }
@@ -484,11 +475,17 @@ sub DelBiblio {
 
 Automatically links headings in a bib record to authorities.
 
+Returns the number of headings changed
+
 =cut
 
 sub BiblioAutoLink {
     my $record        = shift;
     my $frameworkcode = shift;
+    if (!$record) {
+        carp('Undefined record passed to BiblioAutoLink');
+        return 0;
+    }
     my ( $num_headings_changed, %results );
 
     my $linker_module =
@@ -496,7 +493,7 @@ sub BiblioAutoLink {
     unless ( can_load( modules => { $linker_module => undef } ) ) {
         $linker_module = 'C4::Linker::Default';
         unless ( can_load( modules => { $linker_module => undef } ) ) {
-            return 0, 0;
+            return 0;
         }
     }
 
@@ -533,6 +530,10 @@ sub LinkBibHeadingsToAuthorities {
     my $frameworkcode = shift;
     my $allowrelink = shift;
     my %results;
+    if (!$bib) {
+        carp 'LinkBibHeadingsToAuthorities called on undefined bib record';
+        return ( 0, {});
+    }
     require C4::Heading;
     require C4::AuthoritiesMarc;
 
@@ -572,8 +573,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');
@@ -582,7 +582,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] )
@@ -626,6 +626,7 @@ sub LinkBibHeadingsToAuthorities {
                         $heading->auth_type() );
                     $field->add_subfields( '9', $authid );
                     $num_headings_changed++;
+                    $linker->update_cache($heading, $authid);
                     $results{'added'}->{ $heading->display_form() }++;
                 }
             }
@@ -655,7 +656,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.
 
@@ -667,7 +668,7 @@ sub _check_valid_auth_link {
     require C4::AuthoritiesMarc;
 
     my $authorized_heading =
-      C4::AuthoritiesMarc::GetAuthorizedHeading( { 'authid' => $authid } );
+      C4::AuthoritiesMarc::GetAuthorizedHeading( { 'authid' => $authid } ) || '';
 
    return ($field->as_string('abcdefghijklmnopqrstuvwxyz') eq $authorized_heading);
 }
@@ -682,6 +683,11 @@ Get MARC fields from a keyword defined in fieldmapping table.
 
 sub GetRecordValue {
     my ( $field, $record, $frameworkcode ) = @_;
+
+    if (!$record) {
+        carp 'GetRecordValue called with undefined record';
+        return;
+    }
     my $dbh = C4::Context->dbh;
 
     my $sth = $dbh->prepare('SELECT fieldcode, subfieldcode FROM fieldmapping WHERE frameworkcode = ? AND field = ?');
@@ -1060,13 +1066,15 @@ sub GetBiblio {
 sub GetBiblioItemInfosOf {
     my @biblioitemnumbers = @_;
 
-    my $query = '
+    my $biblioitemnumber_values = @biblioitemnumbers ? join( ',', @biblioitemnumbers ) : "''";
+
+    my $query = "
         SELECT biblioitemnumber,
             publicationyear,
             itemtype
         FROM biblioitems
-        WHERE biblioitemnumber IN (' . join( ',', @biblioitemnumbers ) . ')
-    ';
+        WHERE biblioitemnumber IN ($biblioitemnumber_values)
+    ";
     return get_infos_of( $query, 'biblioitemnumber' );
 }
 
@@ -1082,24 +1090,17 @@ $frameworkcode : the framework code to read
 
 =cut
 
-# cache for results of GetMarcStructure -- needed
-# for batch jobs
-our $marc_structure_cache;
-
 sub GetMarcStructure {
     my ( $forlibrarian, $frameworkcode ) = @_;
     my $dbh = C4::Context->dbh;
     $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 $cache = Koha::Cache->get_instance();
+    my $cache_key = "MarcStructure-$forlibrarian-$frameworkcode";
+    my $cached = $cache->get_from_cache($cache_key);
+    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 $sth = $dbh->prepare(
         "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable 
         FROM marc_tag_structure 
@@ -1161,8 +1162,7 @@ sub GetMarcStructure {
         $res->{$tag}->{$subfield}->{maxlength}        = $maxlength;
     }
 
-    $marc_structure_cache->{$forlibrarian}->{$frameworkcode} = $res;
-
+    $cache->set_in_cache($cache_key, $res);
     return $res;
 }
 
@@ -1248,36 +1248,66 @@ sub GetMarcSubfieldStructureFromKohaField {
 
 =head2 GetMarcBiblio
 
-  my $record = GetMarcBiblio($biblionumber, [$embeditems]);
+  my $record = GetMarcBiblio($biblionumber, [$embeditems], [$opac]);
+
+Returns MARC::Record representing a biblio record, or C<undef> if the
+biblionumber doesn't exist.
+
+=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 $opac         = shift || 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, marcxml FROM biblioitems WHERE biblionumber=? ");
     $sth->execute($biblionumber);
     my $row     = $sth->fetchrow_hashref;
+    my $biblioitemnumber = $row->{'biblioitemnumber'};
     my $marcxml = StripNonXmlChars( $row->{'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;
     }
 }
@@ -1313,7 +1343,8 @@ sub GetCOinSBiblio {
 
     # get the coin format
     if ( ! $record ) {
-       return;
+        carp 'GetCOinSBiblio called with undefined record';
+        return;
     }
     my $pos7 = substr $record->leader(), 7, 1;
     my $pos6 = substr $record->leader(), 6, 1;
@@ -1454,14 +1485,24 @@ sub GetCOinSBiblio {
 =head2 GetMarcPrice
 
 return the prices in accordance with the Marc format.
+
+returns 0 if no price found
+returns undef if called without a marc record or with
+an unrecognized marc format
+
 =cut
 
 sub GetMarcPrice {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcPrice called on undefined record';
+        return;
+    }
+
     my @listtags;
     my $subfield;
     
-    if ( $marcflavour eq "MARC21" ) {
+    if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
         @listtags = ('345', '020');
         $subfield="c";
     } elsif ( $marcflavour eq "UNIMARC" ) {
@@ -1488,38 +1529,54 @@ Return the best guess at what the actual price is from a price field.
 
 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 = C4::Budgets->GetCurrency();
+    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;
 }
 
@@ -1529,10 +1586,19 @@ sub MungeMarcPrice {
 return the quantity of a book. Used in acquisition only, when importing a file an iso2709 from a bookseller
 Warning : this is not really in the marc standard. In Unimarc, Electre (the most widely used bookseller) use the 969$a
 
+returns 0 if no quantity found
+returns undef if called without a marc record or with
+an unrecognized marc format
+
 =cut
 
 sub GetMarcQuantity {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcQuantity called on undefined record';
+        return;
+    }
+
     my @listtags;
     my $subfield;
     
@@ -1592,7 +1658,7 @@ sub GetAuthorisedValueDesc {
 
         #---- itemtypes
         if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
-            return getitemtypeinfo($value)->{description};
+            return getitemtypeinfo($value)->{translated_description};
         }
 
         #---- "true" authorized value
@@ -1619,6 +1685,10 @@ Get the control number / record Identifier from the MARC record and return it.
 
 sub GetMarcControlnumber {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcControlnumber called on undefined record';
+        return;
+    }
     my $controlnumber = "";
     # Control number or Record identifier are the same field in MARC21, UNIMARC and NORMARC
     # Keep $marcflavour for possible later use
@@ -1642,32 +1712,25 @@ ISBNs stored in different fields depending on MARC flavour
 
 sub GetMarcISBN {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcISBN called on undefined record';
+        return;
+    }
     my $scope;
     if ( $marcflavour eq "UNIMARC" ) {
         $scope = '010';
     } else {    # assume marc21 if not unimarc
         $scope = '020';
     }
+
     my @marcisbns;
-    my $isbn = "";
-    my $tag  = "";
-    my $marcisbn;
     foreach my $field ( $record->field($scope) ) {
-        my $value = $field->as_string();
+        my $isbn = $field->subfield( 'a' );
         if ( $isbn ne "" ) {
-            $marcisbn = { marcisbn => $isbn, };
-            push @marcisbns, $marcisbn;
-            $isbn = $value;
-        }
-        if ( $isbn ne $value ) {
-            $isbn = $isbn . " " . $value;
+            push @marcisbns, $isbn;
         }
     }
 
-    if ($isbn) {
-        $marcisbn = { marcisbn => $isbn };
-        push @marcisbns, $marcisbn;    #load last tag into array
-    }
     return \@marcisbns;
 }    # end GetMarcISBN
 
@@ -1683,6 +1746,10 @@ ISSNs are stored in different fields depending on MARC flavour
 
 sub GetMarcISSN {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcISSN called on undefined record';
+        return;
+    }
     my $scope;
     if ( $marcflavour eq "UNIMARC" ) {
         $scope = '011';
@@ -1692,7 +1759,8 @@ 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
@@ -1708,6 +1776,10 @@ The note are stored in different fields depending on MARC flavour
 
 sub GetMarcNotes {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcNotes called on undefined record';
+        return;
+    }
     my $scope;
     if ( $marcflavour eq "UNIMARC" ) {
         $scope = '3..';
@@ -1752,6 +1824,10 @@ The subjects are stored in different fields depending on MARC flavour
 
 sub GetMarcSubjects {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcSubjects called on undefined record';
+        return;
+    }
     my ( $mintag, $maxtag, $fields_filter );
     if ( $marcflavour eq "UNIMARC" ) {
         $mintag = "600";
@@ -1766,7 +1842,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);
@@ -1812,7 +1888,7 @@ sub GetMarcSubjects {
                     code      => $code,
                     value     => $value,
                     link_loop => \@this_link_loop,
-                    separator => (scalar @subfields_loop) ? $authoritysep : ''
+                    separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
                 };
             }
         }
@@ -1837,6 +1913,10 @@ The authors are stored in different fields depending on MARC flavour
 
 sub GetMarcAuthors {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcAuthors called on undefined record';
+        return;
+    }
     my ( $mintag, $maxtag, $fields_filter );
 
     # tagslib useful for UNIMARC author reponsabilities
@@ -1853,7 +1933,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;
@@ -1871,9 +1951,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/ ) );
 
@@ -1902,13 +1987,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;
@@ -1925,6 +2011,10 @@ Assumes web resources (not uncommon in MARC21 to omit resource type ind)
 
 sub GetMarcUrls {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcUrls called on undefined record';
+        return;
+    }
 
     my @marcurls;
     for my $field ( $record->field('856') ) {
@@ -1980,11 +2070,16 @@ The series are stored in different fields depending on MARC flavour
 
 sub GetMarcSeries {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcSeries called on undefined record';
+        return;
+    }
+
     my ( $mintag, $maxtag, $fields_filter );
     if ( $marcflavour eq "UNIMARC" ) {
-        $mintag = "600";
-        $maxtag = "619";
-        $fields_filter = '6..';
+        $mintag = "225";
+        $maxtag = "225";
+        $fields_filter = '2..';
     } else {    # marc21/normarc
         $mintag = "440";
         $maxtag = "490";
@@ -1992,7 +2087,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;
@@ -2028,7 +2123,7 @@ sub GetMarcSeries {
                     code      => $code,
                     value     => $value,
                     link_loop => \@link_loop,
-                    separator => (scalar @subfields_loop) ? $authoritysep : '',
+                    separator => (scalar @subfields_loop) ? $AuthoritySeparator : '',
                     volumenum => $volume_number,
                 }
             }
@@ -2049,6 +2144,11 @@ Get all host records (773s MARC21, 461 UNIMARC) from the MARC record and returns
 
 sub GetMarcHosts {
     my ( $record, $marcflavour ) = @_;
+    if (!$record) {
+        carp 'GetMarcHosts called on undefined record';
+        return;
+    }
+
     my ( $tag,$title_subf,$bibnumber_subf,$itemnumber_subf);
     $marcflavour ||="MARC21";
     if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
@@ -2114,20 +2214,25 @@ sub TransformKohaToMarc {
     my $record = MARC::Record->new();
     SetMarcUnicodeFlag( $record, C4::Context->preference("marcflavour") );
     my $db_to_marc = C4::Context->marcfromkohafield;
+    my $tag_hr = {};
     while ( my ($name, $value) = each %$hash ) {
         next unless my $dtm = $db_to_marc->{''}->{$name};
         next unless ( scalar( @$dtm ) );
         my ($tag, $letter) = @$dtm;
+        $tag .= '';
         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->{$tag} //= [];
+            push @{$tag_hr->{$tag}}, [($letter, $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;
 }
@@ -2233,6 +2338,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;
@@ -2266,9 +2373,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 ) };
@@ -2392,7 +2496,7 @@ sub _default_ind_to_space {
 =cut
 
 sub TransformHtmlToMarc {
-    my $cgi    = shift;
+    my ($cgi, $isbiblio) = @_;
 
     my @params = $cgi->param();
 
@@ -2403,26 +2507,25 @@ 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), );
             } else {
@@ -2439,7 +2542,10 @@ sub TransformHtmlToMarc {
 
             if ( $tag < 10 ) {                              # no code for theses fields
                                                             # in MARC editor, 000 contains the leader.
-                if ( $tag eq '000' ) {
+                if ( $tag == $biblionumbertagfield ) {
+                    # We do nothing and let $i be incremented
+                }
+                elsif ( $tag eq '000' ) {
                     # Force a fake leader even if not provided to avoid crashing
                     # during decoding MARC record containing UTF-8 characters
                     $record->leader(
@@ -2458,6 +2564,9 @@ 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; }
@@ -2475,7 +2584,6 @@ sub TransformHtmlToMarc {
             }
             push @fields, $newfield if ($newfield);
         }
-        $i++;
     }
 
     $record->append_fields(@fields);
@@ -2492,12 +2600,19 @@ our $inverted_field_map;
 Extract data from a MARC bib record into a hashref representing
 Koha biblio, biblioitems, and items fields. 
 
+If passed an undefined record will log the error and return an empty
+hash_ref
+
 =cut
 
 sub TransformMarcToKoha {
     my ( $dbh, $record, $frameworkcode, $limit_table ) = @_;
 
-    my $result;
+    my $result = {};
+    if (!defined $record) {
+        carp('TransformMarcToKoha called with undefined record');
+        return $result;
+    }
     $limit_table = $limit_table || 0;
     $frameworkcode = '' unless defined $frameworkcode;
 
@@ -2760,7 +2875,7 @@ sub TransformMarcToKohaOneField {
 
 =head2 ModZebra
 
-  ModZebra( $biblionumber, $op, $server, $oldRecord, $newRecord );
+  ModZebra( $biblionumber, $op, $server );
 
 $biblionumber is the biblionumber we want to index
 
@@ -2768,114 +2883,54 @@ $op is specialUpdate or delete, and is used to know what we want to do
 
 $server is the server that we want to update
 
-$oldRecord is the MARC::Record containing the previous version of the record.  This is used only when 
-NoZebra=1, as NoZebra indexing needs to know the previous version of a record in order to
-do an update.
-
-$newRecord is the MARC::Record containing the new record. It is usefull only when NoZebra=1, and is used to know what to add to the nozebra database. (the record in mySQL being, if it exist, the previous record, the one just before the modif. We need both : the previous and the new one.
-
 =cut
 
 sub ModZebra {
 ###Accepts a $server variable thus we can use it for biblios authorities or other zebra dbs
-    my ( $biblionumber, $op, $server, $oldRecord, $newRecord ) = @_;
+    my ( $biblionumber, $op, $server ) = @_;
     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 misc/cronjobs/zebraqueue_start.pl script
-
-    if ( C4::Context->preference("NoZebra") ) {
-
-        # lock the nozebra table : we will read index lines, update them in Perl process
-        # and write everything in 1 transaction.
-        # lock the table to avoid someone else overwriting what we are doing
-        $dbh->do('LOCK TABLES nozebra WRITE,biblio WRITE,biblioitems WRITE, systempreferences WRITE, auth_types WRITE, auth_header WRITE, auth_subfield_structure READ');
-        my %result;    # the result hash that will be built by deletion / add, and written on mySQL at the end, to improve speed
-        if ( $op eq 'specialUpdate' ) {
-
-            # OK, we have to add or update the record
-            # 1st delete (virtually, in indexes), if record actually exists
-            if ($oldRecord) {
-                %result = _DelBiblioNoZebra( $biblionumber, $oldRecord, $server );
-            }
-
-            # ... add the record
-            %result = _AddBiblioNoZebra( $biblionumber, $newRecord, $server, %result );
-        } else {
+    # the table is emptied by rebuild_zebra.pl script (using the -z switch)
 
-            # it's a deletion, delete the record...
-            # warn "DELETE the record $biblionumber on $server".$record->as_formatted;
-            %result = _DelBiblioNoZebra( $biblionumber, $oldRecord, $server );
-        }
-
-        # ok, now update the database...
-        my $sth = $dbh->prepare("UPDATE nozebra SET biblionumbers=? WHERE server=? AND indexname=? AND value=?");
-        foreach my $key ( keys %result ) {
-            foreach my $index ( keys %{ $result{$key} } ) {
-                $sth->execute( $result{$key}->{$index}, $server, $key, $index );
-            }
-        }
-        $dbh->do('UNLOCK TABLES');
-    } else {
-
-        #
-        # we use zebra, just fill zebraqueue table
-        #
-        my $check_sql = "SELECT COUNT(*) FROM zebraqueue 
-                         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;
-        $check_sth->finish();
-        if ( $count == 0 ) {
-            my $sth = $dbh->prepare("INSERT INTO zebraqueue  (biblio_auth_number,server,operation) VALUES(?,?,?)");
-            $sth->execute( $biblionumber, $server, $op );
-            $sth->finish;
-        }
+    my $check_sql = "SELECT COUNT(*) FROM zebraqueue
+                     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;
+    $check_sth->finish();
+    if ( $count == 0 ) {
+        my $sth = $dbh->prepare("INSERT INTO zebraqueue  (biblio_auth_number,server,operation) VALUES(?,?,?)");
+        $sth->execute( $biblionumber, $server, $op );
+        $sth->finish;
     }
 }
 
-=head2 GetNoZebraIndexes
-
-  %indexes = GetNoZebraIndexes;
-
-return the data from NoZebraIndexes syspref.
-
-=cut
-
-sub GetNoZebraIndexes {
-    my $no_zebra_indexes = C4::Context->preference('NoZebraIndexes');
-    my %indexes;
-  INDEX: foreach my $line ( split /['"],[\n\r]*/, $no_zebra_indexes ) {
-        $line =~ /(.*)=>(.*)/;
-        my $index  = $1;    # initial ' or " is removed afterwards
-        my $fields = $2;
-        $index  =~ s/'|"|\s//g;
-        $fields =~ s/'|"|\s//g;
-        $indexes{$index} = $fields;
-    }
-    return %indexes;
-}
 
 =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) = @_;
-    croak "No MARC record" unless $marc;
+    my ($marc, $biblionumber, $itemnumbers, $opac) = @_;
+    if ( !$marc ) {
+        carp 'EmbedItemsInMarcBiblio: No MARC record passed';
+        return;
+    }
 
     $itemnumbers = [] unless defined $itemnumbers;
 
@@ -2888,10 +2943,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);
@@ -2899,268 +2968,6 @@ sub EmbedItemsInMarcBiblio {
 
 =head1 INTERNAL FUNCTIONS
 
-=head2 _DelBiblioNoZebra($biblionumber,$record,$server);
-
-function to delete a biblio in NoZebra indexes
-This function does NOT delete anything in database : it reads all the indexes entries
-that have to be deleted & delete them in the hash
-
-The SQL part is done either :
- - after the Add if we are modifying a biblio (delete + add again)
- - immediatly after this sub if we are doing a true deletion.
-
-$server can be 'biblioserver' or 'authorityserver' : it indexes biblios or authorities (in the same table, $server being part of the table itself
-
-=cut
-
-sub _DelBiblioNoZebra {
-    my ( $biblionumber, $record, $server ) = @_;
-
-    # Get the indexes
-    my $dbh = C4::Context->dbh;
-
-    # Get the indexes
-    my %index;
-    my $title;
-    if ( $server eq 'biblioserver' ) {
-        %index = GetNoZebraIndexes;
-
-        # get title of the record (to store the 10 first letters with the index)
-        my ( $titletag, $titlesubfield ) = GetMarcFromKohaField( 'biblio.title', '' );    # FIXME: should be GetFrameworkCode($biblionumber) ??
-        $title = lc( $record->subfield( $titletag, $titlesubfield ) );
-    } else {
-
-        # for authorities, the "title" is the $a mainentry
-        my ( $auth_type_tag, $auth_type_sf ) = C4::AuthoritiesMarc::get_auth_type_location();
-        my $authref = C4::AuthoritiesMarc::GetAuthType( $record->subfield( $auth_type_tag, $auth_type_sf ) );
-        warn "ERROR : authtype undefined for " . $record->as_formatted unless $authref;
-        $title = $record->subfield( $authref->{auth_tag_to_report}, 'a' );
-        $index{'mainmainentry'} = $authref->{'auth_tag_to_report'} . 'a';
-        $index{'mainentry'}     = $authref->{'auth_tag_to_report'} . '*';
-        $index{'auth_type'}     = "${auth_type_tag}${auth_type_sf}";
-    }
-
-    my %result;
-
-    # remove blancks comma (that could cause problem when decoding the string for CQL retrieval) and regexp specific values
-    $title =~ s/ |,|;|\[|\]|\(|\)|\*|-|'|=//g;
-
-    # limit to 10 char, should be enough, and limit the DB size
-    $title = substr( $title, 0, 10 );
-
-    #parse each field
-    my $sth2 = $dbh->prepare('SELECT biblionumbers FROM nozebra WHERE server=? AND indexname=? AND value=?');
-    foreach my $field ( $record->fields() ) {
-
-        #parse each subfield
-        next if $field->tag < 10;
-        foreach my $subfield ( $field->subfields() ) {
-            my $tag          = $field->tag();
-            my $subfieldcode = $subfield->[0];
-            my $indexed      = 0;
-
-            # check each index to see if the subfield is stored somewhere
-            # otherwise, store it in __RAW__ index
-            foreach my $key ( keys %index ) {
-
-                #                 warn "examining $key index : ".$index{$key}." for $tag $subfieldcode";
-                if ( $index{$key} =~ /$tag\*/ or $index{$key} =~ /$tag$subfieldcode/ ) {
-                    $indexed = 1;
-                    my $line = lc $subfield->[1];
-
-                    # remove meaningless value in the field...
-                    $line =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|<|>|&|\+|\*|\/|=|:/ /g;
-
-                    # ... and split in words
-                    foreach ( split / /, $line ) {
-                        next unless $_;    # skip  empty values (multiple spaces)
-                                           # if the entry is already here, do nothing, the biblionumber has already be removed
-                        unless ( defined( $result{$key}->{$_} ) && ( $result{$key}->{$_} =~ /$biblionumber,$title\-(\d);/ ) ) {
-
-                            # get the index value if it exist in the nozebra table and remove the entry, otherwise, do nothing
-                            $sth2->execute( $server, $key, $_ );
-                            my $existing_biblionumbers = $sth2->fetchrow;
-
-                            # it exists
-                            if ($existing_biblionumbers) {
-
-                                #                                 warn " existing for $key $_: $existing_biblionumbers";
-                                $result{$key}->{$_} = $existing_biblionumbers;
-                                $result{$key}->{$_} =~ s/$biblionumber,$title\-(\d);//;
-                            }
-                        }
-                    }
-                }
-            }
-
-            # the subfield is not indexed, store it in __RAW__ index anyway
-            unless ($indexed) {
-                my $line = lc $subfield->[1];
-                $line =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|<|>|&|\+|\*|\/|=|:/ /g;
-
-                # ... and split in words
-                foreach ( split / /, $line ) {
-                    next unless $_;    # skip  empty values (multiple spaces)
-                                       # if the entry is already here, do nothing, the biblionumber has already be removed
-                    unless ( $result{'__RAW__'}->{$_} =~ /$biblionumber,$title\-(\d);/ ) {
-
-                        # get the index value if it exist in the nozebra table and remove the entry, otherwise, do nothing
-                        $sth2->execute( $server, '__RAW__', $_ );
-                        my $existing_biblionumbers = $sth2->fetchrow;
-
-                        # it exists
-                        if ($existing_biblionumbers) {
-                            $result{'__RAW__'}->{$_} = $existing_biblionumbers;
-                            $result{'__RAW__'}->{$_} =~ s/$biblionumber,$title\-(\d);//;
-                        }
-                    }
-                }
-            }
-        }
-    }
-    return %result;
-}
-
-=head2 _AddBiblioNoZebra
-
-  _AddBiblioNoZebra($biblionumber, $record, $server, %result);
-
-function to add a biblio in NoZebra indexes
-
-=cut
-
-sub _AddBiblioNoZebra {
-    my ( $biblionumber, $record, $server, %result ) = @_;
-    my $dbh = C4::Context->dbh;
-
-    # Get the indexes
-    my %index;
-    my $title;
-    if ( $server eq 'biblioserver' ) {
-        %index = GetNoZebraIndexes;
-
-        # get title of the record (to store the 10 first letters with the index)
-        my ( $titletag, $titlesubfield ) = GetMarcFromKohaField( 'biblio.title', '' );    # FIXME: should be GetFrameworkCode($biblionumber) ??
-        $title = lc( $record->subfield( $titletag, $titlesubfield ) );
-    } else {
-
-        # warn "server : $server";
-        # for authorities, the "title" is the $a mainentry
-        my ( $auth_type_tag, $auth_type_sf ) = C4::AuthoritiesMarc::get_auth_type_location();
-        my $authref = C4::AuthoritiesMarc::GetAuthType( $record->subfield( $auth_type_tag, $auth_type_sf ) );
-        warn "ERROR : authtype undefined for " . $record->as_formatted unless $authref;
-        $title = $record->subfield( $authref->{auth_tag_to_report}, 'a' );
-        $index{'mainmainentry'} = $authref->{auth_tag_to_report} . 'a';
-        $index{'mainentry'}     = $authref->{auth_tag_to_report} . '*';
-        $index{'auth_type'}     = "${auth_type_tag}${auth_type_sf}";
-    }
-
-    # remove blancks comma (that could cause problem when decoding the string for CQL retrieval) and regexp specific values
-    $title =~ s/ |\.|,|;|\[|\]|\(|\)|\*|-|'|:|=|\r|\n//g;
-
-    # limit to 10 char, should be enough, and limit the DB size
-    $title = substr( $title, 0, 10 );
-
-    #parse each field
-    my $sth2 = $dbh->prepare('SELECT biblionumbers FROM nozebra WHERE server=? AND indexname=? AND value=?');
-    foreach my $field ( $record->fields() ) {
-
-        #parse each subfield
-        ###FIXME: impossible to index a 001-009 value with NoZebra
-        next if $field->tag < 10;
-        foreach my $subfield ( $field->subfields() ) {
-            my $tag          = $field->tag();
-            my $subfieldcode = $subfield->[0];
-            my $indexed      = 0;
-
-            #             warn "INDEXING :".$subfield->[1];
-            # check each index to see if the subfield is stored somewhere
-            # otherwise, store it in __RAW__ index
-            foreach my $key ( keys %index ) {
-
-                #                 warn "examining $key index : ".$index{$key}." for $tag $subfieldcode";
-                if ( $index{$key} =~ /$tag\*/ or $index{$key} =~ /$tag$subfieldcode/ ) {
-                    $indexed = 1;
-                    my $line = lc $subfield->[1];
-
-                    # remove meaningless value in the field...
-                    $line =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|<|>|&|\+|\*|\/|=|:|\r|\n/ /g;
-
-                    # ... and split in words
-                    foreach ( split / /, $line ) {
-                        next unless $_;    # skip  empty values (multiple spaces)
-                                           # if the entry is already here, improve weight
-
-                        #                         warn "managing $_";
-                        if ( exists $result{$key}->{$_} && $result{$key}->{"$_"} =~ /$biblionumber,\Q$title\E\-(\d+);/ ) {
-                            my $weight = $1 + 1;
-                            $result{$key}->{"$_"} =~ s/$biblionumber,\Q$title\E\-(\d+);//g;
-                            $result{$key}->{"$_"} .= "$biblionumber,$title-$weight;";
-                        } else {
-
-                            # get the value if it exist in the nozebra table, otherwise, create it
-                            $sth2->execute( $server, $key, $_ );
-                            my $existing_biblionumbers = $sth2->fetchrow;
-
-                            # it exists
-                            if ($existing_biblionumbers) {
-                                $result{$key}->{"$_"} = $existing_biblionumbers;
-                                my $weight = defined $1 ? $1 + 1 : 1;
-                                $result{$key}->{"$_"} =~ s/$biblionumber,\Q$title\E\-(\d+);//g;
-                                $result{$key}->{"$_"} .= "$biblionumber,$title-$weight;";
-
-                                # create a new ligne for this entry
-                            } else {
-
-                                #                             warn "INSERT : $server / $key / $_";
-                                $dbh->do( 'INSERT INTO nozebra SET server=' . $dbh->quote($server) . ', indexname=' . $dbh->quote($key) . ',value=' . $dbh->quote($_) );
-                                $result{$key}->{"$_"} .= "$biblionumber,$title-1;";
-                            }
-                        }
-                    }
-                }
-            }
-
-            # the subfield is not indexed, store it in __RAW__ index anyway
-            unless ($indexed) {
-                my $line = lc $subfield->[1];
-                $line =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|<|>|&|\+|\*|\/|=|:|\r|\n/ /g;
-
-                # ... and split in words
-                foreach ( split / /, $line ) {
-                    next unless $_;    # skip  empty values (multiple spaces)
-                                       # if the entry is already here, improve weight
-                    my $tmpstr = $result{'__RAW__'}->{"$_"} || "";
-                    if ( $tmpstr =~ /$biblionumber,\Q$title\E\-(\d+);/ ) {
-                        my $weight = $1 + 1;
-                        $result{'__RAW__'}->{"$_"} =~ s/$biblionumber,\Q$title\E\-(\d+);//;
-                        $result{'__RAW__'}->{"$_"} .= "$biblionumber,$title-$weight;";
-                    } else {
-
-                        # get the value if it exist in the nozebra table, otherwise, create it
-                        $sth2->execute( $server, '__RAW__', $_ );
-                        my $existing_biblionumbers = $sth2->fetchrow;
-
-                        # it exists
-                        if ($existing_biblionumbers) {
-                            $result{'__RAW__'}->{"$_"} = $existing_biblionumbers;
-                            my $weight = ( $1 ? $1 : 0 ) + 1;
-                            $result{'__RAW__'}->{"$_"} =~ s/$biblionumber,\Q$title\E\-(\d+);//;
-                            $result{'__RAW__'}->{"$_"} .= "$biblionumber,$title-$weight;";
-
-                            # create a new ligne for this entry
-                        } else {
-                            $dbh->do( 'INSERT INTO nozebra SET server=' . $dbh->quote($server) . ',  indexname="__RAW__",value=' . $dbh->quote($_) );
-                            $result{'__RAW__'}->{"$_"} .= "$biblionumber,$title-1;";
-                        }
-                    }
-                }
-            }
-        }
-    }
-    return %result;
-}
-
 =head2 _koha_marc_update_bib_ids
 
 
@@ -3600,6 +3407,10 @@ sub ModBiblioMarc {
     # pass the MARC::Record to this function, and it will create the records in
     # the marc field
     my ( $record, $biblionumber, $frameworkcode ) = @_;
+    if ( !$record ) {
+        carp 'ModBiblioMarc passed an undefined record';
+        return;
+    }
 
     # Clone record as it gets modified
     $record = $record->clone();
@@ -3641,17 +3452,10 @@ sub ModBiblioMarc {
       $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
     }
 
-    my $oldRecord;
-    if ( C4::Context->preference("NoZebra") ) {
-
-        # only NoZebra indexing needs to have
-        # the previous version of the record
-        $oldRecord = GetMarcBiblio($biblionumber);
-    }
     $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", $oldRecord, $record );
+    ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
     return $biblionumber;
 }
 
@@ -3719,10 +3523,7 @@ sub get_biblio_authorised_values {
 
 =head2 CountBiblioInOrders
 
-=over 4
-$count = &CountBiblioInOrders( $biblionumber);
-
-=back
+    $count = &CountBiblioInOrders( $biblionumber);
 
 This function return count of biblios in orders with $biblionumber 
 
@@ -3742,10 +3543,7 @@ sub CountBiblioInOrders {
 
 =head2 GetSubscriptionsId
 
-=over 4
-$subscriptions = &GetSubscriptionsId($biblionumber);
-
-=back
+    $subscriptions = &GetSubscriptionsId($biblionumber);
 
 This function return an array of subscriptionid with $biblionumber
 
@@ -3765,10 +3563,7 @@ sub GetSubscriptionsId {
 
 =head2 GetHolds
 
-=over 4
-$holds = &GetHolds($biblionumber);
-
-=back
+    $holds = &GetHolds($biblionumber);
 
 This function return the count of holds with $biblionumber
 
@@ -3934,16 +3729,26 @@ sub UpdateTotalIssues {
     my ($biblionumber, $increase, $value) = @_;
     my $totalissues;
 
+    my $record = GetMarcBiblio($biblionumber);
+    unless ($record) {
+        carp "UpdateTotalIssues could not get biblio record";
+        return;
+    }
     my $data = GetBiblioData($biblionumber);
+    unless ($data) {
+        carp "UpdateTotalIssues could not get datas of biblio";
+        return;
+    }
+    my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $data->{'frameworkcode'});
+    unless ($totalissuestag) {
+        return 1; # There is nothing to do
+    }
 
     if (defined $value) {
         $totalissues = $value;
     } else {
         $totalissues = $data->{'totalissues'} + $increase;
     }
-     my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $data->{'frameworkcode'});
-
-     my $record = GetMarcBiblio($biblionumber);
 
      my $field = $record->field($totalissuestag);
      if (defined $field) {
@@ -3954,8 +3759,7 @@ sub UpdateTotalIssues {
          $record->insert_grouped_field($field);
      }
 
-     ModBiblio($record, $biblionumber, $data->{'frameworkcode'});
-     return;
+     return ModBiblio($record, $biblionumber, $data->{'frameworkcode'});
 }
 
 =head2 RemoveAllNsb
@@ -3968,6 +3772,10 @@ Removes all nsb/nse chars from a record
 
 sub RemoveAllNsb {
     my $record = shift;
+    if (!$record) {
+        carp 'RemoveAllNsb called with undefined record';
+        return;
+    }
 
     SetUTF8Flag($record);