Bug 7941 : Fix version numbers in modules
[koha.git] / C4 / Biblio.pm
index 50830ec..1ab4fff 100644 (file)
@@ -34,11 +34,13 @@ use C4::Dates qw/format_date/;
 use C4::Log;    # logaction
 use C4::ClassSource;
 use C4::Charset;
+use C4::Linker;
+use C4::OAI::Sets;
 
 use vars qw($VERSION @ISA @EXPORT);
 
 BEGIN {
-    $VERSION = 1.00;
+    $VERSION = 3.07.00.049;
 
     require Exporter;
     @ISA = qw( Exporter );
@@ -81,6 +83,7 @@ BEGIN {
       &GetXmlBiblio
       &GetCOinSBiblio
       &GetMarcPrice
+      &MungeMarcPrice
       &GetMarcQuantity
 
       &GetAuthorisedValueDesc
@@ -111,6 +114,7 @@ BEGIN {
     # To link headings in a bib record
     # to authority records.
     push @EXPORT, qw(
+      &BiblioAutoLink
       &LinkBibHeadingsToAuthorities
     );
 
@@ -129,6 +133,7 @@ BEGIN {
       &TransformHtmlToMarc
       &TransformHtmlToXml
       &GetNoZebraIndexes
+      prepare_host_field
     );
 }
 
@@ -266,6 +271,11 @@ sub AddBiblio {
     # now add the record
     ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
 
+    # update OAI-PMH sets
+    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
+        C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
+    }
+
     logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
     return ( $biblionumber, $biblioitemnumber );
 }
@@ -337,6 +347,12 @@ sub ModBiblio {
     # modify the other koha tables
     _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
     _koha_modify_biblioitem_nonmarc( $dbh, $oldbiblio );
+
+    # update OAI-PMH sets
+    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
+        C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
+    }
+
     return 1;
 }
 
@@ -415,6 +431,13 @@ sub DelBiblio {
         C4::Serials::DelSubscription( $subscription->{subscriptionid} );
     }
 
+    # We delete any existing holds
+    require C4::Reserves;
+    my ($count, $reserves) = C4::Reserves::GetReservesFromBiblionumber($biblionumber);
+    foreach my $res ( @$reserves ) {
+        C4::Reserves::CancelReserve( $res->{'biblionumber'}, $res->{'itemnumber'}, $res->{'borrowernumber'} );
+    }
+
     # 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
@@ -450,9 +473,42 @@ sub DelBiblio {
     return;
 }
 
+
+=head2 BiblioAutoLink
+
+  my $headings_linked = BiblioAutoLink($record, $frameworkcode)
+
+Automatically links headings in a bib record to authorities.
+
+=cut
+
+sub BiblioAutoLink {
+    my $record        = shift;
+    my $frameworkcode = shift;
+    my ( $num_headings_changed, %results );
+
+    my $linker_module =
+      "C4::Linker::" . ( C4::Context->preference("LinkerModule") || 'Default' );
+    eval { eval "require $linker_module"; };
+    if ($@) {
+        $linker_module = 'C4::Linker::Default';
+        eval "require $linker_module";
+    }
+    if ($@) {
+        return 0, 0;
+    }
+
+    my $linker = $linker_module->new(
+        { 'options' => C4::Context->preference("LinkerOptions") } );
+    my ( $headings_changed, undef ) =
+      LinkBibHeadingsToAuthorities( $linker, $record, $frameworkcode, C4::Context->preference("CatalogModuleRelink") || '' );
+    # By default we probably don't want to relink things when cataloging
+    return $headings_changed;
+}
+
 =head2 LinkBibHeadingsToAuthorities
 
-  my $headings_linked = LinkBibHeadingsToAuthorities($marc);
+  my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink]);
 
 Links bib headings to authority records by checking
 each authority-controlled field in the C<MARC::Record>
@@ -460,9 +516,9 @@ object C<$marc>, looking for a matching authority record,
 and setting the linking subfield $9 to the ID of that
 authority record.  
 
-If no matching authority exists, or if multiple
-authorities match, no $9 will be added, and any 
-existing one inthe field will be deleted.
+If $allowrelink is false, existing authids will never be
+replaced, regardless of the values of LinkerKeepStale and
+LinkerRelink.
 
 Returns the number of heading links changed in the
 MARC record.
@@ -470,38 +526,112 @@ MARC record.
 =cut
 
 sub LinkBibHeadingsToAuthorities {
+    my $linker        = shift;
+    my $bib           = shift;
+    my $frameworkcode = shift;
+    my $allowrelink = shift;
+    my %results;
     require C4::Heading;
-    my $bib = shift;
+    require C4::AuthoritiesMarc;
 
+    $allowrelink = 1 unless defined $allowrelink;
     my $num_headings_changed = 0;
     foreach my $field ( $bib->fields() ) {
-        my $heading = C4::Heading->new_from_bib_field($field);
+        my $heading = C4::Heading->new_from_bib_field( $field, $frameworkcode );
         next unless defined $heading;
 
         # check existing $9
         my $current_link = $field->subfield('9');
 
-        # look for matching authorities
-        my $authorities = $heading->authorities();
+        if ( defined $current_link && (!$allowrelink || !C4::Context->preference('LinkerRelink')) )
+        {
+            $results{'linked'}->{ $heading->display_form() }++;
+            next;
+        }
 
-        # want only one exact match
-        if ( $#{$authorities} == 0 ) {
-            my $authority = MARC::Record->new_from_usmarc( $authorities->[0] );
-            my $authid    = $authority->field('001')->data();
-            next if defined $current_link and $current_link eq $authid;
+        my ( $authid, $fuzzy ) = $linker->get_link($heading);
+        if ($authid) {
+            $results{ $fuzzy ? 'fuzzy' : 'linked' }
+              ->{ $heading->display_form() }++;
+            next if defined $current_link and $current_link == $authid;
 
             $field->delete_subfield( code => '9' ) if defined $current_link;
             $field->add_subfields( '9', $authid );
             $num_headings_changed++;
-        } else {
-            if ( defined $current_link ) {
+        }
+        else {
+            if ( defined $current_link
+                && (!$allowrelink || C4::Context->preference('LinkerKeepStale')) )
+            {
+                $results{'fuzzy'}->{ $heading->display_form() }++;
+            }
+            elsif ( C4::Context->preference('AutoCreateAuthorities') ) {
+                my $authtypedata =
+                  C4::AuthoritiesMarc::GetAuthType( $heading->auth_type() );
+                my $marcrecordauth = MARC::Record->new();
+                if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
+                    $marcrecordauth->leader('     nz  a22     o  4500');
+                    SetMarcUnicodeFlag( $marcrecordauth, 'MARC21' );
+                }
+                my $authfield =
+                  MARC::Field->new( $authtypedata->{auth_tag_to_report},
+                    '', '', "a" => "" . $field->subfield('a') );
+                map {
+                    $authfield->add_subfields( $_->[0] => $_->[1] )
+                      if ( $_->[0] =~ /[A-z]/ && $_->[0] ne "a" )
+                } $field->subfields();
+                $marcrecordauth->insert_fields_ordered($authfield);
+
+# bug 2317: ensure new authority knows it's using UTF-8; currently
+# only need to do this for MARC21, as MARC::Record->as_xml_record() handles
+# automatically for UNIMARC (by not transcoding)
+# FIXME: AddAuthority() instead should simply explicitly require that the MARC::Record
+# use UTF-8, but as of 2008-08-05, did not want to introduce that kind
+# of change to a core API just before the 3.0 release.
+
+                if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
+                    $marcrecordauth->insert_fields_ordered(
+                        MARC::Field->new(
+                            '667', '', '',
+                            'a' => "Machine generated authority record."
+                        )
+                    );
+                    my $cite =
+                        $bib->author() . ", "
+                      . $bib->title_proper() . ", "
+                      . $bib->publication_date() . " ";
+                    $cite =~ s/^[\s\,]*//;
+                    $cite =~ s/[\s\,]*$//;
+                    $cite =
+                        "Work cat.: ("
+                      . C4::Context->preference('MARCOrgCode') . ")"
+                      . $bib->subfield( '999', 'c' ) . ": "
+                      . $cite;
+                    $marcrecordauth->insert_fields_ordered(
+                        MARC::Field->new( '670', '', '', 'a' => $cite ) );
+                }
+
+           #          warn "AUTH RECORD ADDED : ".$marcrecordauth->as_formatted;
+
+                $authid =
+                  C4::AuthoritiesMarc::AddAuthority( $marcrecordauth, '',
+                    $heading->auth_type() );
+                $field->add_subfields( '9', $authid );
+                $num_headings_changed++;
+                $results{'added'}->{ $heading->display_form() }++;
+            }
+            elsif ( defined $current_link ) {
                 $field->delete_subfield( code => '9' );
                 $num_headings_changed++;
+                $results{'unlinked'}->{ $heading->display_form() }++;
+            }
+            else {
+                $results{'unlinked'}->{ $heading->display_form() }++;
             }
         }
 
     }
-    return $num_headings_changed;
+    return $num_headings_changed, \%results;
 }
 
 =head2 GetRecordValue
@@ -952,7 +1082,7 @@ sub GetMarcStructure {
     }
 
     $sth = $dbh->prepare(
-        "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab,mandatory,repeatable,authorised_value,authtypecode,value_builder,kohafield,seealso,hidden,isurl,link,defaultvalue 
+        "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab,mandatory,repeatable,authorised_value,authtypecode,value_builder,kohafield,seealso,hidden,isurl,link,defaultvalue,maxlength
          FROM   marc_subfield_structure 
          WHERE  frameworkcode=? 
          ORDER BY tagfield,tagsubfield
@@ -971,10 +1101,12 @@ sub GetMarcStructure {
     my $isurl;
     my $link;
     my $defaultvalue;
+    my $maxlength;
 
     while (
         (   $tag,          $subfield,      $liblibrarian, $libopac, $tab,    $mandatory, $repeatable, $authorised_value,
-            $authtypecode, $value_builder, $kohafield,    $seealso, $hidden, $isurl,     $link,       $defaultvalue
+            $authtypecode, $value_builder, $kohafield,    $seealso, $hidden, $isurl,     $link,       $defaultvalue,
+            $maxlength
         )
         = $sth->fetchrow
       ) {
@@ -991,6 +1123,7 @@ sub GetMarcStructure {
         $res->{$tag}->{$subfield}->{isurl}            = $isurl;
         $res->{$tag}->{$subfield}->{'link'}           = $link;
         $res->{$tag}->{$subfield}->{defaultvalue}     = $defaultvalue;
+        $res->{$tag}->{$subfield}->{maxlength}        = $maxlength;
     }
 
     $marc_structure_cache->{$forlibrarian}->{$frameworkcode} = $res;
@@ -1273,12 +1406,56 @@ sub GetMarcPrice {
     for my $field ( $record->field(@listtags) ) {
         for my $subfield_value  ($field->subfield($subfield)){
             #check value
+            $subfield_value = MungeMarcPrice( $subfield_value );
             return $subfield_value if ($subfield_value);
         }
     }
     return 0; # no price found
 }
 
+=head2 MungeMarcPrice
+
+Return the best guess at what the actual price is from a price field.
+=cut
+
+sub MungeMarcPrice {
+    my ( $price ) = @_;
+
+    return unless ( $price =~ m/\d/ ); ## No digits means no price.
+
+    ## Look for the currency symbol of the active currency, if it's there,
+    ## start the price string right after the symbol. This allows us to prefer
+    ## this native currency price over other currency prices, if possible.
+    my $active_currency = C4::Context->dbh->selectrow_hashref( 'SELECT * FROM currency WHERE active = 1', {} );
+    my $symbol = quotemeta( $active_currency->{'symbol'} );
+    if ( $price =~ m/$symbol/ ) {
+        my @parts = split(/$symbol/, $price );
+        $price = $parts[1];
+    }
+
+    ## Grab the first number in the string ( can use commas or periods for thousands separator and/or decimal separator )
+    ( $price ) = $price =~ m/([\d\,\.]+[[\,\.]\d\d]?)/;
+
+    ## Split price into array on periods and commas
+    my @parts = split(/[\,\.]/, $price);
+
+    ## If the last grouping of digits is more than 2 characters, assume there is no decimal value and put it back.
+    my $decimal = pop( @parts );
+    if ( length( $decimal ) > 2 ) {
+        push( @parts, $decimal );
+        $decimal = '';
+    }
+
+    $price = join('', @parts );
+
+    if ( $decimal ) {
+     $price .= ".$decimal";
+    }
+
+    return $price;
+}
+
+
 =head2 GetMarcQuantity
 
 return the quantity of a book. Used in acquisition only, when importing a file an iso2709 from a bookseller
@@ -1557,7 +1734,7 @@ sub GetMarcSubjects {
 
             # ignore $9
             my @this_link_loop = @link_loop;
-            push @subfields_loop, { code => $code, value => $value, link_loop => \@this_link_loop, separator => $separator } unless ( $subject_subfield->[0] eq 9 );
+            push @subfields_loop, { code => $code, value => $value, link_loop => \@this_link_loop, separator => $separator } unless ( $subject_subfield->[0] eq 9 || $subject_subfield->[0] eq '0' );
             $counter++;
         }
 
@@ -1641,7 +1818,7 @@ sub GetMarcAuthors {
                 link_loop => \@this_link_loop,
                 separator => $separator
               }
-              unless ( $authors_subfield->[0] eq '9' );
+              unless ( $authors_subfield->[0] eq '9' || $authors_subfield->[0] eq '0');
             $count_auth++;
         }
         push @marcauthors, { MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop };
@@ -1864,6 +2041,7 @@ sub TransformKohaToMarc {
     my $db_to_marc = C4::Context->marcfromkohafield;
     while ( my ($name, $value) = each %$hash ) {
         next unless my $dtm = $db_to_marc->{''}->{$name};
+        next unless ( scalar( @$dtm ) );
         my ($tag, $letter) = @$dtm;
         foreach my $value ( split(/\s?\|\s?/, $value, -1) ) {
             if ( my $field = $record->field($tag) ) {
@@ -1891,6 +2069,7 @@ sub PrepHostMarcField {
     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
     $marcflavour ||="MARC21";
     
+    require C4::Items;
     my $hostrecord = GetMarcBiblio($hostbiblionumber);
        my $item = C4::Items::GetItem($hostitemnumber);
        
@@ -3137,7 +3316,8 @@ sub _koha_modify_biblioitem_nonmarc {
         cn_item         = ?,
         cn_suffix       = ?,
         cn_sort         = ?,
-        totalissues     = ?
+        totalissues     = ?,
+    ean             = ?
         where biblioitemnumber = ?
         ";
     my $sth = $dbh->prepare($query);
@@ -3149,6 +3329,7 @@ sub _koha_modify_biblioitem_nonmarc {
         $biblioitem->{'pages'},            $biblioitem->{'bnotes'},           $biblioitem->{'size'},                  $biblioitem->{'place'},
         $biblioitem->{'lccn'},             $biblioitem->{'url'},              $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'},
         $biblioitem->{'cn_item'},          $biblioitem->{'cn_suffix'},        $cn_sort,                               $biblioitem->{'totalissues'},
+    $biblioitem->{'ean'},
         $biblioitem->{'biblioitemnumber'}
     );
     if ( $dbh->errstr ) {
@@ -3200,7 +3381,8 @@ sub _koha_add_biblioitem {
         cn_item         = ?,
         cn_suffix       = ?,
         cn_sort         = ?,
-        totalissues     = ?
+        totalissues     = ?,
+    ean             = ?
         ";
     my $sth = $dbh->prepare($query);
     $sth->execute(
@@ -3211,7 +3393,7 @@ sub _koha_add_biblioitem {
         $biblioitem->{'pages'},            $biblioitem->{'bnotes'},           $biblioitem->{'size'},                  $biblioitem->{'place'},
         $biblioitem->{'lccn'},             $biblioitem->{'marc'},             $biblioitem->{'url'},                   $biblioitem->{'biblioitems.cn_source'},
         $biblioitem->{'cn_class'},         $biblioitem->{'cn_item'},          $biblioitem->{'cn_suffix'},             $cn_sort,
-        $biblioitem->{'totalissues'}
+        $biblioitem->{'totalissues'},      $biblioitem->{'ean'}
     );
     my $bibitemnum = $dbh->{'mysql_insertid'};
 
@@ -3335,9 +3517,12 @@ Function exported, but should NOT be used, unless you really know what you're do
 =cut
 
 sub ModBiblioMarc {
-
-    # pass the MARC::Record to this function, and it will create the records in the marc field
+    # pass the MARC::Record to this function, and it will create the records in
+    # the marc field
     my ( $record, $biblionumber, $frameworkcode ) = @_;
+
+    # Clone record as it gets modified
+    $record = $record->clone();
     my $dbh    = C4::Context->dbh;
     my @fields = $record->fields();
     if ( !$frameworkcode ) {
@@ -3518,9 +3703,134 @@ sub GetHolds {
     return ($holds);
 }
 
+=head2 prepare_host_field
+
+$marcfield = prepare_host_field( $hostbiblioitem, $marcflavour );
+Generate the host item entry for an analytic child entry
+
+=cut
+
+sub prepare_host_field {
+    my ( $hostbiblio, $marcflavour ) = @_;
+    $marcflavour ||= C4::Context->preference('marcflavour');
+    my $host = GetMarcBiblio($hostbiblio);
+    # unfortunately as_string does not 'do the right thing'
+    # if field returns undef
+    my %sfd;
+    my $field;
+    my $host_field;
+    if ( $marcflavour eq 'MARC21' || $marcflavour eq 'NORMARC' ) {
+        if ( $field = $host->field('100') || $host->field('110') || $host->field('11') ) {
+            my $s = $field->as_string('ab');
+            if ($s) {
+                $sfd{a} = $s;
+            }
+        }
+        if ( $field = $host->field('245') ) {
+            my $s = $field->as_string('a');
+            if ($s) {
+                $sfd{t} = $s;
+            }
+        }
+        if ( $field = $host->field('260') ) {
+            my $s = $field->as_string('abc');
+            if ($s) {
+                $sfd{d} = $s;
+            }
+        }
+        if ( $field = $host->field('240') ) {
+            my $s = $field->as_string();
+            if ($s) {
+                $sfd{b} = $s;
+            }
+        }
+        if ( $field = $host->field('022') ) {
+            my $s = $field->as_string('a');
+            if ($s) {
+                $sfd{x} = $s;
+            }
+        }
+        if ( $field = $host->field('020') ) {
+            my $s = $field->as_string('a');
+            if ($s) {
+                $sfd{z} = $s;
+            }
+        }
+        if ( $field = $host->field('001') ) {
+            $sfd{w} = $field->data(),;
+        }
+        $host_field = MARC::Field->new( 773, '0', ' ', %sfd );
+        return $host_field;
+    }
+    elsif ( $marcflavour eq 'UNIMARC' ) {
+        #author
+        if ( $field = $host->field('700') || $host->field('710') || $host->field('720') ) {
+            my $s = $field->as_string('ab');
+            if ($s) {
+                $sfd{a} = $s;
+            }
+        }
+        #title
+        if ( $field = $host->field('200') ) {
+            my $s = $field->as_string('a');
+            if ($s) {
+                $sfd{t} = $s;
+            }
+        }
+        #place of publicaton
+        if ( $field = $host->field('210') ) {
+            my $s = $field->as_string('a');
+            if ($s) {
+                $sfd{c} = $s;
+            }
+        }
+        #date of publication
+        if ( $field = $host->field('210') ) {
+            my $s = $field->as_string('d');
+            if ($s) {
+                $sfd{d} = $s;
+            }
+        }
+        #edition statement
+        if ( $field = $host->field('205') ) {
+            my $s = $field->as_string();
+            if ($s) {
+                $sfd{a} = $s;
+            }
+        }
+        #URL
+        if ( $field = $host->field('856') ) {
+            my $s = $field->as_string('u');
+            if ($s) {
+                $sfd{u} = $s;
+            }
+        }
+        #ISSN
+        if ( $field = $host->field('011') ) {
+            my $s = $field->as_string('a');
+            if ($s) {
+                $sfd{x} = $s;
+            }
+        }
+        #ISBN
+        if ( $field = $host->field('010') ) {
+            my $s = $field->as_string('a');
+            if ($s) {
+                $sfd{y} = $s;
+            }
+        }
+        if ( $field = $host->field('001') ) {
+            $sfd{0} = $field->data(),;
+        }
+        $host_field = MARC::Field->new( 461, '0', ' ', %sfd );
+        return $host_field;
+    }
+    return;
+}
 
 1;
 
+
 __END__
 
 =head1 AUTHOR