Bug 17969: (QA followup) Add POD
[koha.git] / C4 / Biblio.pm
index cc517a0..54a6731 100644 (file)
@@ -41,6 +41,10 @@ use C4::Debug;
 use Koha::Caches;
 use Koha::Authority::Types;
 use Koha::Acquisition::Currencies;
+use Koha::Biblio::Metadata;
+use Koha::Biblio::Metadatas;
+use Koha::Holds;
+use Koha::ItemTypes;
 use Koha::SearchEngine;
 use Koha::Libraries;
 
@@ -60,19 +64,13 @@ BEGIN {
 
     # to get something
     push @EXPORT, qw(
-      GetBiblio
       GetBiblioData
       GetMarcBiblio
       GetBiblioItemData
       GetBiblioItemInfosOf
       GetBiblioItemByBiblioNumber
-      GetBiblioFromItemNumber
-      GetBiblionumberFromItemnumber
 
       &GetRecordValue
-      &GetFieldMapping
-      &SetFieldMapping
-      &DeleteFieldMapping
 
       &GetISBDView
 
@@ -103,7 +101,6 @@ BEGIN {
 
       &CountItemsIssued
       &CountBiblioInOrders
-      &GetSubscriptionsId
     );
 
     # To modify something
@@ -157,11 +154,11 @@ Biblio.pm contains functions for managing storage and editing of bibliographic d
 
 =item 2. as raw MARC in the Zebra index and storage engine
 
-=item 3. as MARC XML in biblioitems.marcxml
+=item 3. as MARC XML in biblio_metadata.metadata
 
 =back
 
-In the 3.0 version of Koha, the authoritative record-level information is in biblioitems.marcxml
+In the 3.0 version of Koha, the authoritative record-level information is in biblio_metadata.metadata
 
 Because the data isn't completely normalized there's a chance for information to get out of sync. The design choice to go with a un-normalized schema was driven by performance and stability concerns. However, if this occur, it can be considered as a bug : The API is (or should be) complete & the only entry point for all biblio/items managements.
 
@@ -181,7 +178,7 @@ Because of this design choice, the process of managing storage and editing is a
 
 =item 2. _koha_* - low-level internal functions for managing the koha tables
 
-=item 3. Marc management function : as the MARC record is stored in biblioitems.marcxml, some subs dedicated to it's management are in this package. They should be used only internally by Biblio.pm, the only official entry points being AddBiblio, AddItem, ModBiblio, ModItem.
+=item 3. Marc management function : as the MARC record is stored in biblio_metadata.metadata, some subs dedicated to it's management are in this package. They should be used only internally by Biblio.pm, the only official entry points being AddBiblio, AddItem, ModBiblio, ModItem.
 
 =item 4. Zebra functions used to update the Zebra index
 
@@ -189,7 +186,7 @@ Because of this design choice, the process of managing storage and editing is a
 
 =back
 
-The MARC record (in biblioitems.marcxml) contains the complete marc record, including items. It also contains the biblionumber. That is the reason why it is not stored directly by AddBiblio, with all other fields . To save a biblio, we need to :
+The MARC record (in biblio_metadata.metadata) contains the complete marc record, including items. It also contains the biblionumber. That is the reason why it is not stored directly by AddBiblio, with all other fields . To save a biblio, we need to :
 
 =over 4
 
@@ -201,20 +198,6 @@ The MARC record (in biblioitems.marcxml) contains the complete marc record, incl
 
 =back
 
-When dealing with items, we must :
-
-=over 4
-
-=item 1. save the item in items table, that gives us an itemnumber
-
-=item 2. add the itemnumber to the item MARC field
-
-=item 3. overwrite the MARC record (with the added item) into biblioitems.marcxml
-
-When modifying a biblio or an item, the behaviour is quite similar.
-
-=back
-
 =head1 EXPORTED FUNCTIONS
 
 =head2 AddBiblio
@@ -230,7 +213,7 @@ framework code.
 This function also accepts a third, optional argument: a hashref
 to additional options.  The only defined option is C<defer_marc_save>,
 which if present and mapped to a true value, causes C<AddBiblio>
-to omit the call to save the MARC in C<bibilioitems.marcxml>
+to omit the call to save the MARC in C<biblio_metadata.metadata>
 This option is provided B<only>
 for the use of scripts such as C<bulkmarcimport.pl> that may need
 to do some manipulation of the MARC record for item parsing before
@@ -310,7 +293,7 @@ sub ModBiblio {
     }
 
     if ( C4::Context->preference("CataloguingLog") ) {
-        my $newrecord = GetMarcBiblio($biblionumber);
+        my $newrecord = GetMarcBiblio({ biblionumber => $biblionumber });
         logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
     }
 
@@ -421,10 +404,11 @@ sub DelBiblio {
     }
 
     # We delete any existing holds
+    my $biblio = Koha::Biblios->find( $biblionumber );
+    my $holds = $biblio->holds;
     require C4::Reserves;
-    my $reserves = C4::Reserves::GetReservesFromBiblionumber({ biblionumber => $biblionumber });
-    foreach my $res ( @$reserves ) {
-        C4::Reserves::CancelReserve({ reserve_id => $res->{'reserve_id'} });
+    while ( my $hold = $holds->next ) {
+        C4::Reserves::CancelReserve({ reserve_id => $hold->reserve_id }); # TODO Replace with $hold->cancel
     }
 
     # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
@@ -443,6 +427,7 @@ sub DelBiblio {
         return $error if $error;
     }
 
+
     # delete biblio from Koha tables and save in deletedbiblio
     # must do this *after* _koha_delete_biblioitems, otherwise
     # delete cascade will prevent deletedbiblioitems rows
@@ -697,66 +682,6 @@ sub GetRecordValue {
     return \@result;
 }
 
-=head2 SetFieldMapping
-
-  SetFieldMapping($framework, $field, $fieldcode, $subfieldcode);
-
-Set a Field to MARC mapping value, if it already exists we don't add a new one.
-
-=cut
-
-sub SetFieldMapping {
-    my ( $framework, $field, $fieldcode, $subfieldcode ) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $sth = $dbh->prepare('SELECT * FROM fieldmapping WHERE fieldcode = ? AND subfieldcode = ? AND frameworkcode = ? AND field = ?');
-    $sth->execute( $fieldcode, $subfieldcode, $framework, $field );
-    if ( not $sth->fetchrow_hashref ) {
-        my @args;
-        $sth = $dbh->prepare('INSERT INTO fieldmapping (fieldcode, subfieldcode, frameworkcode, field) VALUES(?,?,?,?)');
-
-        $sth->execute( $fieldcode, $subfieldcode, $framework, $field );
-    }
-}
-
-=head2 DeleteFieldMapping
-
-  DeleteFieldMapping($id);
-
-Delete a field mapping from an $id.
-
-=cut
-
-sub DeleteFieldMapping {
-    my ($id) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $sth = $dbh->prepare('DELETE FROM fieldmapping WHERE id = ?');
-    $sth->execute($id);
-}
-
-=head2 GetFieldMapping
-
-  GetFieldMapping($frameworkcode);
-
-Get all field mappings for a specified frameworkcode
-
-=cut
-
-sub GetFieldMapping {
-    my ($framework) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $sth = $dbh->prepare('SELECT * FROM fieldmapping where frameworkcode = ?');
-    $sth->execute($framework);
-
-    my @return;
-    while ( my $row = $sth->fetchrow_hashref ) {
-        push @return, $row;
-    }
-    return \@return;
-}
-
 =head2 GetBiblioData
 
   $data = &GetBiblioData($biblionumber);
@@ -844,60 +769,6 @@ sub GetBiblioItemByBiblioNumber {
     return @results;
 }
 
-=head2 GetBiblionumberFromItemnumber
-
-
-=cut
-
-sub GetBiblionumberFromItemnumber {
-    my ($itemnumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare("Select biblionumber FROM items WHERE itemnumber = ?");
-
-    $sth->execute($itemnumber);
-    my ($result) = $sth->fetchrow;
-    return ($result);
-}
-
-=head2 GetBiblioFromItemNumber
-
-  $item = &GetBiblioFromItemNumber($itemnumber,$barcode);
-
-Looks up the item with the given itemnumber. if undef, try the barcode.
-
-C<&itemnodata> returns a reference-to-hash whose keys are the fields
-from the C<biblio>, C<biblioitems>, and C<items> tables in the Koha
-database.
-
-=cut
-
-#'
-sub GetBiblioFromItemNumber {
-    my ( $itemnumber, $barcode ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth;
-    if ($itemnumber) {
-        $sth = $dbh->prepare(
-            "SELECT * FROM items 
-            LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
-            LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
-             WHERE items.itemnumber = ?"
-        );
-        $sth->execute($itemnumber);
-    } else {
-        $sth = $dbh->prepare(
-            "SELECT * FROM items 
-            LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
-            LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
-             WHERE items.barcode = ?"
-        );
-        $sth->execute($barcode);
-    }
-    my $data = $sth->fetchrow_hashref;
-    $sth->finish;
-    return ($data);
-}
-
 =head2 GetISBDView 
 
   $isbd = &GetISBDView({
@@ -1034,25 +905,6 @@ sub GetISBDView {
     return $res;
 }
 
-=head2 GetBiblio
-
-  my $biblio = &GetBiblio($biblionumber);
-
-=cut
-
-sub GetBiblio {
-    my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare("SELECT * FROM biblio WHERE biblionumber = ?");
-    my $count          = 0;
-    my @results;
-    $sth->execute($biblionumber);
-    if ( my $data = $sth->fetchrow_hashref ) {
-        return $data;
-    }
-    return;
-}    # sub GetBiblio
-
 =head2 GetBiblioItemInfosOf
 
   GetBiblioItemInfosOf(@biblioitemnumbers);
@@ -1064,6 +916,7 @@ sub GetBiblioItemInfosOf {
 
     my $biblioitemnumber_values = @biblioitemnumbers ? join( ',', @biblioitemnumbers ) : "''";
 
+    my $dbh = C4::Context->dbh;
     my $query = "
         SELECT biblioitemnumber,
             publicationyear,
@@ -1071,7 +924,7 @@ sub GetBiblioItemInfosOf {
         FROM biblioitems
         WHERE biblioitemnumber IN ($biblioitemnumber_values)
     ";
-    return get_infos_of( $query, 'biblioitemnumber' );
+    return $dbh->selectall_hashref($query, 'biblioitemnumber');
 }
 
 =head1 FUNCTIONS FOR HANDLING MARC MANAGEMENT
@@ -1283,11 +1136,18 @@ sub GetMarcSubfieldStructureFromKohaField {
 
 =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>
@@ -1308,9 +1168,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';
@@ -1318,11 +1185,12 @@ sub GetMarcBiblio {
     }
 
     my $dbh          = C4::Context->dbh;
-    my $sth          = $dbh->prepare("SELECT biblioitemnumber, marcxml FROM biblioitems WHERE biblionumber=? ");
+    my $sth          = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=? ");
     $sth->execute($biblionumber);
     my $row     = $sth->fetchrow_hashref;
     my $biblioitemnumber = $row->{'biblioitemnumber'};
-    my $marcxml = StripNonXmlChars( $row->{'marcxml'} );
+    my $marcxml = GetXmlBiblio( $biblionumber );
+    $marcxml = StripNonXmlChars( $marcxml );
     my $frameworkcode = GetFrameworkCode($biblionumber);
     MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
     my $record = MARC::Record->new();
@@ -1351,17 +1219,24 @@ sub GetMarcBiblio {
 
   my $marcxml = GetXmlBiblio($biblionumber);
 
-Returns biblioitems.marcxml of the biblionumber passed in parameter.
+Returns biblio_metadata.metadata/marcxml of the biblionumber passed in parameter.
 The XML should only contain biblio information (item information is no longer stored in marcxml field)
 
 =cut
 
 sub GetXmlBiblio {
     my ($biblionumber) = @_;
-    my $dbh            = C4::Context->dbh;
-    my $sth            = $dbh->prepare("SELECT marcxml FROM biblioitems WHERE biblionumber=? ");
-    $sth->execute($biblionumber);
-    my ($marcxml) = $sth->fetchrow;
+    my $dbh = C4::Context->dbh;
+    return unless $biblionumber;
+    my ($marcxml) = $dbh->selectrow_array(
+        q|
+        SELECT metadata
+        FROM biblio_metadata
+        WHERE biblionumber=?
+            AND format='marcxml'
+            AND marcflavour=?
+    |, undef, $biblionumber, C4::Context->preference('marcflavour')
+    );
     return $marcxml;
 }
 
@@ -1560,6 +1435,7 @@ sub GetMarcPrice {
 =head2 MungeMarcPrice
 
 Return the best guess at what the actual price is from a price field.
+
 =cut
 
 sub MungeMarcPrice {
@@ -1692,7 +1568,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
@@ -2051,6 +1928,7 @@ sub GetMarcUrls {
         }
         my @urls = $field->subfield('u');
         foreach my $url (@urls) {
+            $url =~ s/^\s+|\s+$//g; # trim
             my $marcurl;
             if ( $marcflavour eq 'MARC21' ) {
                 my $s3   = $field->subfield('3');
@@ -2317,7 +2195,7 @@ sub PrepHostMarcField {
     $marcflavour ||="MARC21";
     
     require C4::Items;
-    my $hostrecord = GetMarcBiblio($hostbiblionumber);
+    my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
        my $item = C4::Items::GetItem($hostitemnumber);
        
        my $hostmarcfield;
@@ -2966,7 +2844,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] );
@@ -3216,9 +3096,6 @@ sub _koha_modify_biblio {
 
   my ($biblioitemnumber,$error) = _koha_modify_biblioitem_nonmarc( $dbh, $biblioitem );
 
-Updates biblioitems row except for marc and marcxml, which should be changed
-via ModBiblioMarc
-
 =cut
 
 sub _koha_modify_biblioitem_nonmarc {
@@ -3366,6 +3243,8 @@ sub _koha_delete_biblio {
     my $sth = $dbh->prepare("SELECT * FROM biblio WHERE biblionumber=?");
     $sth->execute($biblionumber);
 
+    # FIXME There is a transaction in _koha_delete_biblio_metadata
+    # But actually all the following should be done inside a single transaction
     if ( my $data = $sth->fetchrow_hashref ) {
 
         # save the record in deletedbiblio
@@ -3383,6 +3262,8 @@ sub _koha_delete_biblio {
         $bkup_sth->execute(@bind);
         $bkup_sth->finish;
 
+        _koha_delete_biblio_metadata( $biblionumber );
+
         # delete the biblio
         my $sth2 = $dbh->prepare("DELETE FROM biblio WHERE biblionumber=?");
         $sth2->execute($biblionumber);
@@ -3444,6 +3325,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
@@ -3505,9 +3411,20 @@ sub ModBiblioMarc {
       $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
     }
 
-    $sth = $dbh->prepare("UPDATE biblioitems SET marcxml=? WHERE biblionumber=?");
-    $sth->execute( $record->as_xml_record($encoding), $biblionumber );
-    $sth->finish;
+    my $metadata = {
+        biblionumber => $biblionumber,
+        format       => 'marcxml',
+        marcflavour  => C4::Context->preference('marcflavour'),
+    };
+    # FIXME To replace with ->find_or_create?
+    if ( my $m_rs = Koha::Biblio::Metadatas->find($metadata) ) {
+        $m_rs->metadata( $record->as_xml_record($encoding) );
+        $m_rs->store;
+    } else {
+        my $m_rs = Koha::Biblio::Metadata->new($metadata);
+        $m_rs->metadata( $record->as_xml_record($encoding) );
+        $m_rs->store;
+    }
     ModZebra( $biblionumber, "specialUpdate", "biblioserver", $record );
     return $biblionumber;
 }
@@ -3532,26 +3449,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 );
@@ -3562,7 +3459,7 @@ Generate the host item entry for an analytic child entry
 sub prepare_host_field {
     my ( $hostbiblio, $marcflavour ) = @_;
     $marcflavour ||= C4::Context->preference('marcflavour');
-    my $host = GetMarcBiblio($hostbiblio);
+    my $host = GetMarcBiblio({ biblionumber => $hostbiblio });
     # unfortunately as_string does not 'do the right thing'
     # if field returns undef
     my %sfd;
@@ -3700,17 +3597,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
     }
@@ -3718,7 +3616,7 @@ sub UpdateTotalIssues {
     if (defined $value) {
         $totalissues = $value;
     } else {
-        $totalissues = $data->{'totalissues'} + $increase;
+        $totalissues = $biblioitem->totalissues + $increase;
     }
 
      my $field = $record->field($totalissuestag);
@@ -3730,7 +3628,7 @@ sub UpdateTotalIssues {
          $record->insert_grouped_field($field);
      }
 
-     return ModBiblio($record, $biblionumber, $data->{'frameworkcode'});
+     return ModBiblio($record, $biblionumber, $biblio->frameworkcode);
 }
 
 =head2 RemoveAllNsb