use Modern::Perl;
-use vars qw(@ISA @EXPORT);
+use vars qw(@ISA @EXPORT_OK);
BEGIN {
require Exporter;
@ISA = qw(Exporter);
- @EXPORT = qw(
+ @EXPORT_OK = qw(
AddBiblio
GetBiblioData
- GetMarcBiblio
GetISBDView
GetMarcControlnumber
- GetMarcNotes
GetMarcISBN
GetMarcISSN
GetMarcSubjects
- GetMarcAuthors
GetMarcSeries
- GetMarcHosts
GetMarcUrls
GetUsedMarcStructure
GetXmlBiblio
GetMarcQuantity
GetAuthorisedValueDesc
GetMarcStructure
+ GetMarcSubfieldStructure
IsMarcStructureInternal
GetMarcFromKohaField
GetMarcSubfieldStructureFromKohaField
CountItemsIssued
ModBiblio
ModZebra
+ EmbedItemsInMarcBiblio
UpdateTotalIssues
RemoveAllNsb
DelBiblio
BiblioAutoLink
LinkBibHeadingsToAuthorities
+ ApplyMarcOverlayRules
TransformMarcToKoha
TransformHtmlToMarc
TransformHtmlToXml
# those functions are exported but should not be used
# 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(
+ push @EXPORT_OK, qw(
ModBiblioMarc
);
}
-use Carp;
-use Try::Tiny;
+use Carp qw( carp );
+use Try::Tiny qw( catch try );
-use Encode qw( decode is_utf8 );
+use Encode;
use List::MoreUtils qw( uniq );
use MARC::Record;
use MARC::File::USMARC;
use MARC::File::XML;
-use POSIX qw(strftime);
-use Module::Load::Conditional qw(can_load);
+use POSIX qw( strftime );
+use Module::Load::Conditional qw( can_load );
use C4::Koha;
-use C4::Log; # logaction
+use C4::Log qw( logaction ); # logaction
use C4::Budgets;
-use C4::ClassSource;
-use C4::Charset;
+use C4::ClassSource qw( GetClassSort GetClassSource );
+use C4::Charset qw(
+ nsb_clean
+ SetMarcUnicodeFlag
+ SetUTF8Flag
+);
use C4::Linker;
use C4::OAI::Sets;
-use C4::Debug;
+use C4::Items qw( GetMarcItem );
+use Koha::Logger;
use Koha::Caches;
use Koha::Authority::Types;
use Koha::Acquisition::Currencies;
+use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
use Koha::Biblio::Metadatas;
use Koha::Holds;
use Koha::ItemTypes;
+use Koha::MarcOverlayRules;
use Koha::Plugins;
use Koha::SearchEngine;
+use Koha::SearchEngine::Indexer;
use Koha::Libraries;
use Koha::Util::MARC;
-use vars qw($debug $cgi_debug);
-
-
=head1 NAME
C4::Biblio - cataloging management functions
bib to add, while the second argument is the desired MARC
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<biblio_metadata.metadata>
-This option is provided B<only>
-for the use of scripts such as C<bulkmarcimport.pl> that may need
-to do some manipulation of the MARC record for item parsing before
-saving it and which cannot afford the performance hit of saving
-the MARC record twice. Consequently, do not use that option
-unless you can guarantee that C<ModBiblioMarc> will be called.
+The C<$options> argument is a hashref with additional parameters:
+
+=over 4
+
+=item B<defer_marc_save>: used when ModBiblioMarc is handled by the caller
+
+=item B<skip_record_index>: used when the indexing schedulling will be handled by the caller
+
+=back
=cut
sub AddBiblio {
- my $record = shift;
- my $frameworkcode = shift;
- my $options = @_ ? shift : undef;
- my $defer_marc_save = 0;
+ my ( $record, $frameworkcode, $options ) = @_;
+
+ $options //= {};
+ my $skip_record_index = $options->{skip_record_index} || 0;
+ my $defer_marc_save = $options->{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;
- }
-
- if (C4::Context->preference('BiblioAddsAuthorities')) {
- BiblioAutoLink( $record, $frameworkcode );
- }
- my ( $biblionumber, $biblioitemnumber, $error );
- my $dbh = C4::Context->dbh;
+ my $schema = Koha::Database->schema;
+ my ( $biblionumber, $biblioitemnumber );
+ try {
+ $schema->txn_do(sub {
+
+ # transform the data into koha-table style data
+ SetUTF8Flag($record);
+ my $olddata = TransformMarcToKoha({ record => $record, limit_table => 'no_items' });
+
+ my $biblio = Koha::Biblio->new(
+ {
+ frameworkcode => $frameworkcode,
+ author => $olddata->{author},
+ title => $olddata->{title},
+ subtitle => $olddata->{subtitle},
+ medium => $olddata->{medium},
+ part_number => $olddata->{part_number},
+ part_name => $olddata->{part_name},
+ unititle => $olddata->{unititle},
+ notes => $olddata->{notes},
+ serial =>
+ ( $olddata->{serial} || $olddata->{seriestitle} ? 1 : 0 ),
+ seriestitle => $olddata->{seriestitle},
+ copyrightdate => $olddata->{copyrightdate},
+ datecreated => \'NOW()',
+ abstract => $olddata->{abstract},
+ }
+ )->store;
+ $biblionumber = $biblio->biblionumber;
+ Koha::Exceptions::ObjectNotCreated->throw unless $biblio;
+
+ my ($cn_sort) = GetClassSort( $olddata->{'biblioitems.cn_source'}, $olddata->{'cn_class'}, $olddata->{'cn_item'} );
+ my $biblioitem = Koha::Biblioitem->new(
+ {
+ biblionumber => $biblionumber,
+ volume => $olddata->{volume},
+ number => $olddata->{number},
+ itemtype => $olddata->{itemtype},
+ isbn => $olddata->{isbn},
+ issn => $olddata->{issn},
+ publicationyear => $olddata->{publicationyear},
+ publishercode => $olddata->{publishercode},
+ volumedate => $olddata->{volumedate},
+ volumedesc => $olddata->{volumedesc},
+ collectiontitle => $olddata->{collectiontitle},
+ collectionissn => $olddata->{collectionissn},
+ collectionvolume => $olddata->{collectionvolume},
+ editionstatement => $olddata->{editionstatement},
+ editionresponsibility => $olddata->{editionresponsibility},
+ illus => $olddata->{illus},
+ pages => $olddata->{pages},
+ notes => $olddata->{bnotes},
+ size => $olddata->{size},
+ place => $olddata->{place},
+ lccn => $olddata->{lccn},
+ url => $olddata->{url},
+ cn_source => $olddata->{'biblioitems.cn_source'},
+ cn_class => $olddata->{cn_class},
+ cn_item => $olddata->{cn_item},
+ cn_suffix => $olddata->{cn_suff},
+ cn_sort => $cn_sort,
+ totalissues => $olddata->{totalissues},
+ ean => $olddata->{ean},
+ agerestriction => $olddata->{agerestriction},
+ }
+ )->store;
+ Koha::Exceptions::ObjectNotCreated->throw unless $biblioitem;
+ $biblioitemnumber = $biblioitem->biblioitemnumber;
- # transform the data into koha-table style data
- SetUTF8Flag($record);
- my $olddata = TransformMarcToKoha( $record, $frameworkcode );
- ( $biblionumber, $error ) = _koha_add_biblio( $dbh, $olddata, $frameworkcode );
- $olddata->{'biblionumber'} = $biblionumber;
- ( $biblioitemnumber, $error ) = _koha_add_biblioitem( $dbh, $olddata );
+ _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
- _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
+ # update MARC subfield that stores biblioitems.cn_sort
+ _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
- # update MARC subfield that stores biblioitems.cn_sort
- _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
+ if (C4::Context->preference('AutoLinkBiblios')) {
+ BiblioAutoLink( $record, $frameworkcode );
+ }
- # now add the record
- ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
+ # now add the record
+ ModBiblioMarc( $record, $biblionumber, { skip_record_index => $skip_record_index } ) unless $defer_marc_save;
- # update OAI-PMH sets
- if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
- C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
- }
+ # update OAI-PMH sets
+ if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
+ C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
+ }
- _after_biblio_action_hooks({ action => 'create', biblio_id => $biblionumber });
+ _after_biblio_action_hooks({ action => 'create', biblio_id => $biblionumber });
- logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
+ logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
+ });
+ } catch {
+ warn $_;
+ ( $biblionumber, $biblioitemnumber ) = ( undef, undef );
+ };
return ( $biblionumber, $biblioitemnumber );
}
=head2 ModBiblio
- ModBiblio( $record,$biblionumber,$frameworkcode, $disable_autolink);
+ ModBiblio($record, $biblionumber, $frameworkcode, $options);
Replace an existing bib record identified by C<$biblionumber>
with one supplied by the MARC::Record object C<$record>. The embedded
which fields are used to store embedded item, biblioitem,
and biblionumber data for indexing.
-Unless C<$disable_autolink> is passed ModBiblio will relink record headings
+The C<$options> argument is a hashref with additional parameters:
+
+=over 4
+
+=item C<overlay_context>
+
+This parameter is forwarded to L</ApplyMarcOverlayRules> where it is used for
+selecting the current rule set if MARCOverlayRules is enabled.
+See L</ApplyMarcOverlayRules> for more details.
+
+=item C<disable_autolink>
+
+Unless C<disable_autolink> is passed ModBiblio will relink record headings
to authorities based on settings in the system preferences. This flag allows
us to not relink records when the authority linker is saving modifications.
+=item C<skip_holds_queue>
+
+Unless C<skip_holds_queue> is passed, ModBiblio will trigger the BatchUpdateBiblioHoldsQueue
+task to rebuild the holds queue for the biblio if I<RealTimeHoldsQueue> is enabled.
+
+=back
+
Returns 1 on success 0 on failure
=cut
sub ModBiblio {
- my ( $record, $biblionumber, $frameworkcode, $disable_autolink ) = @_;
+ my ( $record, $biblionumber, $frameworkcode, $options ) = @_;
+
+ $options //= {};
+ my $skip_record_index = $options->{skip_record_index} || 0;
+
if (!$record) {
carp 'No record passed to ModBiblio';
return 0;
}
if ( C4::Context->preference("CataloguingLog") ) {
- my $newrecord = GetMarcBiblio({ biblionumber => $biblionumber });
- logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
+ my $biblio = Koha::Biblios->find($biblionumber);
+ logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $biblio->metadata->record->as_formatted );
}
- if ( !$disable_autolink && C4::Context->preference('BiblioAddsAuthorities') ) {
+ if ( !$options->{disable_autolink} && C4::Context->preference('AutoLinkBiblios') ) {
BiblioAutoLink( $record, $frameworkcode );
}
_strip_item_fields($record, $frameworkcode);
+ # apply overlay rules
+ if ( C4::Context->preference('MARCOverlayRules')
+ && $biblionumber
+ && defined $options
+ && exists $options->{overlay_context} )
+ {
+ $record = ApplyMarcOverlayRules(
+ {
+ biblionumber => $biblionumber,
+ record => $record,
+ overlay_context => $options->{overlay_context},
+ }
+ );
+ }
+
# update biblionumber and biblioitemnumber in MARC
# FIXME - this is assuming a 1 to 1 relationship between
# biblios and biblioitems
_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
# load the koha-table data object
- my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
+ my $oldbiblio = TransformMarcToKoha({ record => $record });
# update MARC subfield that stores biblioitems.cn_sort
_koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
# update the MARC record (that now contains biblio and items) with the new record data
- &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
+ ModBiblioMarc( $record, $biblionumber, { skip_record_index => $skip_record_index } );
# modify the other koha tables
_koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
}
+ Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+ {
+ biblio_ids => [ $biblionumber ]
+ }
+ ) unless $options->{skip_holds_queue} or !C4::Context->preference('RealTimeHoldsQueue');
+
return 1;
}
=head2 DelBiblio
- my $error = &DelBiblio($biblionumber);
+ my $error = &DelBiblio($biblionumber, $params);
Exported function (core API) for deleting a biblio in koha.
Deletes biblio record from Zebra and Koha tables (biblio & biblioitems)
return:
C<$error> : undef unless an error occurs
+I<$params> is a hashref containing extra parameters. Valid keys are:
+
+=over 4
+
+=item B<skip_holds_queue>: used when the holds queue update will be handled by the caller
+
+=item B<skip_record_index>: used when the indexing schedulling will be handled by the caller
+
+=back
=cut
sub DelBiblio {
- my ($biblionumber) = @_;
+ my ($biblionumber, $params) = @_;
my $biblio = Koha::Biblios->find( $biblionumber );
return unless $biblio; # Should we throw an exception instead?
return $error if $error;
- # We delete attached subscriptions
- require C4::Serials;
- my $subscriptions = C4::Serials::GetFullSubscriptionsFromBiblionumber($biblionumber);
- foreach my $subscription (@$subscriptions) {
- C4::Serials::DelSubscription( $subscription->{subscriptionid} );
- }
-
# We delete any existing holds
my $holds = $biblio->holds;
while ( my $hold = $holds->next ) {
- $hold->cancel;
+ # no need to update the holds queue on each step, we'll do it at the end
+ $hold->cancel({ skip_holds_queue => 1 });
}
- # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
- # for at least 2 reasons :
- # - 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)
- ModZebra( $biblionumber, "recordDelete", "biblioserver" );
+ unless ( $params->{skip_record_index} ){
+ my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+ $indexer->index_records( $biblionumber, "recordDelete", "biblioserver" );
+ }
# delete biblioitems and items from Koha tables and save in deletedbiblioitems,deleteditems
$sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
+ Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+ {
+ biblio_ids => [ $biblionumber ]
+ }
+ ) unless $params->{skip_holds_queue} or !C4::Context->preference('RealTimeHoldsQueue');
+
return;
}
sub BiblioAutoLink {
my $record = shift;
my $frameworkcode = shift;
+ my $verbose = shift;
if (!$record) {
carp('Undefined record passed to BiblioAutoLink');
return 0;
my $linker = $linker_module->new(
{ 'options' => C4::Context->preference("LinkerOptions") } );
- my ( $headings_changed, undef ) =
- LinkBibHeadingsToAuthorities( $linker, $record, $frameworkcode, C4::Context->preference("CatalogModuleRelink") || '' );
+ my ( $headings_changed, $results ) =
+ LinkBibHeadingsToAuthorities( $linker, $record, $frameworkcode, C4::Context->preference("CatalogModuleRelink") || '', undef, $verbose );
# By default we probably don't want to relink things when cataloging
- return $headings_changed;
+ return $headings_changed, $results;
}
=head2 LinkBibHeadingsToAuthorities
- my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink]);
+ my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink, $tagtolink, $verbose]);
Links bib headings to authority records by checking
each authority-controlled field in the C<MARC::Record>
my $bib = shift;
my $frameworkcode = shift;
my $allowrelink = shift;
+ my $tagtolink = shift;
+ my $verbose = shift;
my %results;
if (!$bib) {
carp 'LinkBibHeadingsToAuthorities called on undefined bib record';
$allowrelink = 1 unless defined $allowrelink;
my $num_headings_changed = 0;
foreach my $field ( $bib->fields() ) {
+ if ( defined $tagtolink ) {
+ next unless $field->tag() == $tagtolink ;
+ }
my $heading = C4::Heading->new_from_field( $field, $frameworkcode );
next unless defined $heading;
if ( defined $current_link && (!$allowrelink || !C4::Context->preference('LinkerRelink')) )
{
$results{'linked'}->{ $heading->display_form() }++;
+ push(@{$results{'details'}}, { tag => $field->tag(), authid => $current_link, status => 'UNCHANGED'}) if $verbose;
next;
}
- my ( $authid, $fuzzy ) = $linker->get_link($heading);
+ my ( $authid, $fuzzy, $match_count ) = $linker->get_link($heading);
if ($authid) {
$results{ $fuzzy ? 'fuzzy' : 'linked' }
->{ $heading->display_form() }++;
- next if defined $current_link and $current_link == $authid;
+ if(defined $current_link and $current_link == $authid) {
+ push(@{$results{'details'}}, { tag => $field->tag(), authid => $current_link, status => 'UNCHANGED'}) if $verbose;
+ next;
+ }
$field->delete_subfield( code => '9' ) if defined $current_link;
$field->add_subfields( '9', $authid );
$num_headings_changed++;
+ push(@{$results{'details'}}, { tag => $field->tag(), authid => $authid, status => 'LOCAL_FOUND'}) if $verbose;
}
else {
+ my $authority_type = Koha::Authority::Types->find( $heading->auth_type() );
if ( defined $current_link
&& (!$allowrelink || C4::Context->preference('LinkerKeepStale')) )
{
$results{'fuzzy'}->{ $heading->display_form() }++;
+ push(@{$results{'details'}}, { tag => $field->tag(), authid => $current_link, status => 'UNCHANGED'}) if $verbose;
}
elsif ( C4::Context->preference('AutoCreateAuthorities') ) {
if ( _check_valid_auth_link( $current_link, $field ) ) {
$results{'linked'}->{ $heading->display_form() }++;
}
- else {
+ elsif ( !$match_count ) {
my $authority_type = Koha::Authority::Types->find( $heading->auth_type() );
my $marcrecordauth = MARC::Record->new();
if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
$marcrecordauth->insert_fields_ordered(
MARC::Field->new(
'667', '', '',
- 'a' => "Machine generated authority record."
+ 'a' => C4::Context->preference('GenerateAuthorityField667')
)
);
my $cite =
$cite =~ s/^[\s\,]*//;
$cite =~ s/[\s\,]*$//;
$cite =
- "Work cat.: ("
+ C4::Context->preference('GenerateAuthorityField670') . ": ("
. ( $library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') ) . ")"
. $bib->subfield( '999', 'c' ) . ": "
. $cite;
$num_headings_changed++;
$linker->update_cache($heading, $authid);
$results{'added'}->{ $heading->display_form() }++;
+ push(@{$results{'details'}}, { tag => $field->tag(), authid => $authid, status => 'CREATED'}) if $verbose;
}
}
elsif ( defined $current_link ) {
if ( _check_valid_auth_link( $current_link, $field ) ) {
$results{'linked'}->{ $heading->display_form() }++;
+ push(@{$results{'details'}}, { tag => $field->tag(), authid => $authid, status => 'UNCHANGED'}) if $verbose;
}
else {
$field->delete_subfield( code => '9' );
$num_headings_changed++;
$results{'unlinked'}->{ $heading->display_form() }++;
+ push(@{$results{'details'}}, { tag => $field->tag(), authid => undef, status => 'NONE_FOUND', auth_type => $heading->auth_type(), tag_to_report => $authority_type->auth_tag_to_report}) if $verbose;
}
}
else {
$results{'unlinked'}->{ $heading->display_form() }++;
+ push(@{$results{'details'}}, { tag => $field->tag(), authid => undef, status => 'NONE_FOUND', auth_type => $heading->auth_type(), tag_to_report => $authority_type->auth_tag_to_report}) if $verbose;
}
}
}
+ push(@{$results{'details'}}, { tag => '', authid => undef, status => 'UNCHANGED'}) unless %results;
return $num_headings_changed, \%results;
}
my ( $authid, $field ) = @_;
require C4::AuthoritiesMarc;
- my $authorized_heading =
- C4::AuthoritiesMarc::GetAuthorizedHeading( { 'authid' => $authid } ) || '';
- return ($field->as_string('abcdefghijklmnopqrstuvwxyz') eq $authorized_heading);
+ return C4::AuthoritiesMarc::CompareFieldWithAuthority( { 'field' => $field, 'authid' => $authid } );
}
=head2 GetBiblioData
ORDER BY tagfield"
);
$sth->execute($frameworkcode);
- my ( $liblibrarian, $libopac, $tag, $res, $tab, $mandatory, $repeatable, $important, $ind1_defaultvalue, $ind2_defaultvalue );
+ my ( $liblibrarian, $libopac, $tag, $res, $mandatory, $repeatable, $important, $ind1_defaultvalue, $ind2_defaultvalue );
while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable, $important, $ind1_defaultvalue, $ind2_defaultvalue ) = $sth->fetchrow ) {
$res->{$tag}->{lib} = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
$res->{$tag}->{ind2_defaultvalue} = $ind2_defaultvalue;
}
- $sth = $dbh->prepare(
- "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab,mandatory,repeatable,authorised_value,authtypecode,value_builder,kohafield,seealso,hidden,isurl,link,defaultvalue,maxlength,important
- FROM marc_subfield_structure
- WHERE frameworkcode=?
- ORDER BY tagfield,tagsubfield
- "
- );
-
- $sth->execute($frameworkcode);
-
- my $subfield;
- my $authorised_value;
- my $authtypecode;
- my $value_builder;
- my $kohafield;
- my $seealso;
- my $hidden;
- 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,
- $maxlength, $important
- )
- = $sth->fetchrow
- ) {
- $res->{$tag}->{$subfield}->{lib} = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
- $res->{$tag}->{$subfield}->{tab} = $tab;
- $res->{$tag}->{$subfield}->{mandatory} = $mandatory;
- $res->{$tag}->{$subfield}->{important} = $important;
- $res->{$tag}->{$subfield}->{repeatable} = $repeatable;
- $res->{$tag}->{$subfield}->{authorised_value} = $authorised_value;
- $res->{$tag}->{$subfield}->{authtypecode} = $authtypecode;
- $res->{$tag}->{$subfield}->{value_builder} = $value_builder;
- $res->{$tag}->{$subfield}->{kohafield} = $kohafield;
- $res->{$tag}->{$subfield}->{seealso} = $seealso;
- $res->{$tag}->{$subfield}->{hidden} = $hidden;
- $res->{$tag}->{$subfield}->{isurl} = $isurl;
- $res->{$tag}->{$subfield}->{'link'} = $link;
- $res->{$tag}->{$subfield}->{defaultvalue} = $defaultvalue;
- $res->{$tag}->{$subfield}->{maxlength} = $maxlength;
+ my $mss = Koha::MarcSubfieldStructures->search( { frameworkcode => $frameworkcode } )->unblessed;
+ for my $m (@$mss) {
+ $res->{ $m->{tagfield} }->{ $m->{tagsubfield} } = {
+ lib => ( $forlibrarian or !$m->{libopac} ) ? $m->{liblibrarian} : $m->{libopac},
+ subfield => $m->{tagsubfield},
+ %$m
+ };
}
$cache->set_in_cache($cache_key, $res);
FROM marc_subfield_structure
WHERE tab > -1
AND frameworkcode = ?
- ORDER BY tagfield, tagsubfield
+ ORDER BY tagfield, display_order, tagsubfield
};
my $sth = C4::Context->dbh->prepare($query);
$sth->execute($frameworkcode);
FROM marc_subfield_structure
WHERE frameworkcode = ?
AND kohafield > ''
- ORDER BY frameworkcode,tagfield,tagsubfield
+ ORDER BY frameworkcode, tagfield, display_order, tagsubfield
|, { Slice => {} }, $frameworkcode );
# Now map the output to a hash structure
my $subfield_structure = {};
return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
}
-=head2 GetMarcBiblio
-
- my $record = GetMarcBiblio({
- biblionumber => $biblionumber,
- embed_items => $embeditems,
- opac => $opac,
- borcat => $patron_category });
-
-Returns MARC::Record representing a biblio record, or C<undef> if the
-biblionumber doesn't exist.
-
-Both embed_items and opac are optional.
-If embed_items is passed and is 1, items are embedded.
-If opac is passed and is 1, the record is filtered as needed.
-
-=over 4
-
-=item C<$biblionumber>
-
-the biblionumber
-
-=item C<$embeditems>
-
-set to true to include item information.
-
-=item C<$opac>
-
-set to true to make the result suited for OPAC view. This causes things like
-OpacHiddenItems to be applied.
-
-=item C<$borcat>
-
-If the OpacHiddenItemsExceptions system preference is set, this patron category
-can be used to make visible OPAC items which would be normally hidden.
-It only makes sense in combination both embed_items and opac values true.
-
-=back
-
-=cut
-
-sub GetMarcBiblio {
- 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;
- my $borcat = $params->{borcat} // q{};
-
- if (not defined $biblionumber) {
- carp 'GetMarcBiblio called with undefined biblionumber';
- return;
- }
-
- my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=? ");
- $sth->execute($biblionumber);
- my $row = $sth->fetchrow_hashref;
- my $biblioitemnumber = $row->{'biblioitemnumber'};
- my $marcxml = GetXmlBiblio( $biblionumber );
- $marcxml = StripNonXmlChars( $marcxml );
- my $frameworkcode = GetFrameworkCode($biblionumber);
- MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
- my $record = MARC::Record->new();
-
- if ($marcxml) {
- $record = eval {
- MARC::Record::new_from_xml( $marcxml, "UTF-8",
- C4::Context->preference('marcflavour') );
- };
- if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
- return unless $record;
-
- C4::Biblio::_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber,
- $biblioitemnumber );
- C4::Biblio::EmbedItemsInMarcBiblio({
- marc_record => $record,
- biblionumber => $biblionumber,
- opac => $opac,
- borcat => $borcat })
- if ($embeditems);
-
- return $record;
- }
- else {
- return;
- }
-}
-
=head2 GetXmlBiblio
my $marcxml = GetXmlBiblio($biblionumber);
my @listtags;
my $subfield;
- if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
+ if ( $marcflavour eq "MARC21" ) {
@listtags = ('345', '020');
$subfield="c";
} elsif ( $marcflavour eq "UNIMARC" ) {
return $itemtype ? $itemtype->translated_description : q||;
}
+ if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "cn_source" ) {
+ my $source = GetClassSource($value);
+ return $source ? $source->{description} : q||;
+ }
+
#---- "true" authorized value
$category = $tagslib->{$tag}->{$subfield}->{'authorised_value'};
}
return;
}
my $controlnumber = "";
- # Control number or Record identifier are the same field in MARC21, UNIMARC and NORMARC
+ # Control number or Record identifier are the same field in MARC21 and UNIMARC
# Keep $marcflavour for possible later use
- if ($marcflavour eq "MARC21" || $marcflavour eq "UNIMARC" || $marcflavour eq "NORMARC") {
+ if ($marcflavour eq "MARC21" || $marcflavour eq "UNIMARC" ) {
my $controlnumberField = $record->field('001');
if ($controlnumberField) {
$controlnumber = $controlnumberField->data();
if ( $marcflavour eq "UNIMARC" ) {
$scope = '011';
}
- else { # assume MARC21 or NORMARC
+ else { # assume MARC21
$scope = '022';
}
my @marcissns;
return \@marcissns;
} # end GetMarcISSN
-=head2 GetMarcNotes
-
- $marcnotesarray = GetMarcNotes( $record, $marcflavour );
-
- Get all notes from the MARC record and returns them in an array.
- The notes are stored in different fields depending on MARC flavour.
- MARC21 5XX $u subfields receive special attention as they are URIs.
-
-=cut
-
-sub GetMarcNotes {
- my ( $record, $marcflavour, $opac ) = @_;
- if (!$record) {
- carp 'GetMarcNotes called on undefined record';
- return;
- }
-
- my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
- my @marcnotes;
-
- #MARC21 specs indicate some notes should be private if first indicator 0
- my %maybe_private = (
- 541 => 1,
- 542 => 1,
- 561 => 1,
- 583 => 1,
- 590 => 1
- );
-
- my %blacklist = map { $_ => 1 }
- split( /,/, C4::Context->preference('NotesBlacklist'));
- foreach my $field ( $record->field($scope) ) {
- my $tag = $field->tag();
- next if $blacklist{ $tag };
- next if $opac && $maybe_private{$tag} && !$field->indicator(1);
- if( $marcflavour ne 'UNIMARC' && $field->subfield('u') ) {
- # Field 5XX$u always contains URI
- # Examples: 505u, 506u, 510u, 514u, 520u, 530u, 538u, 540u, 542u, 552u, 555u, 561u, 563u, 583u
- # We first push the other subfields, then all $u's separately
- # Leave further actions to the template (see e.g. opac-detail)
- my $othersub =
- join '', ( 'a' .. 't', 'v' .. 'z', '0' .. '9' ); # excl 'u'
- push @marcnotes, { marcnote => $field->as_string($othersub) };
- foreach my $sub ( $field->subfield('u') ) {
- $sub =~ s/^\s+|\s+$//g; # trim
- push @marcnotes, { marcnote => $sub };
- }
- } else {
- push @marcnotes, { marcnote => $field->as_string() };
- }
- }
- return \@marcnotes;
-}
-
=head2 GetMarcSubjects
$marcsubjcts = GetMarcSubjects($record,$marcflavour);
$mintag = "600";
$maxtag = "611";
$fields_filter = '6..';
- } else { # marc21/normarc
+ } else { # marc21
$mintag = "600";
$maxtag = "699";
$fields_filter = '6..';
push @link_loop, {
limit => $subject_limit,
'link' => $linkvalue,
- operator => (scalar @link_loop) ? ' and ' : undef
+ operator => (scalar @link_loop) ? ' AND ' : undef
};
}
my @this_link_loop = @link_loop;
return \@marcsubjects;
} #end getMARCsubjects
-=head2 GetMarcAuthors
-
- authors = GetMarcAuthors($record,$marcflavour);
-
-Get all authors from the MARC record and returns them in an array.
-The authors are stored in different fields depending on MARC flavour
-
-=cut
-
-sub GetMarcAuthors {
- my ( $record, $marcflavour ) = @_;
- if (!$record) {
- carp 'GetMarcAuthors called on undefined record';
- return;
- }
- my ( $mintag, $maxtag, $fields_filter );
-
- # tagslib useful only for UNIMARC author responsibilities
- my $tagslib;
- if ( $marcflavour eq "UNIMARC" ) {
- # FIXME : we don't have the framework available, we take the default framework. May be buggy on some setups, will be usually correct.
- $tagslib = GetMarcStructure( 1, '', { unsafe => 1 });
- $mintag = "700";
- $maxtag = "712";
- $fields_filter = '7..';
- } else { # marc21/normarc
- $mintag = "700";
- $maxtag = "720";
- $fields_filter = '7..';
- }
-
- my @marcauthors;
- my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
-
- foreach my $field ( $record->field($fields_filter) ) {
- next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
- my @subfields_loop;
- my @link_loop;
- my @subfields = $field->subfields();
- my $count_auth = 0;
-
- # if there is an authority link, build the link with Koha-Auth-Number: subfield9
- my $subfield9 = $field->subfield('9');
- if ($subfield9) {
- my $linkvalue = $subfield9;
- $linkvalue =~ s/(\(|\))//g;
- @link_loop = ( { 'limit' => 'an', 'link' => $linkvalue } );
- }
-
- # 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/ ) );
-
- my $code = $authors_subfield->[0];
- my $value = $authors_subfield->[1];
- my $linkvalue = $value;
- $linkvalue =~ s/(\(|\))//g;
- # UNIMARC author responsibility
- if ( $marcflavour eq 'UNIMARC' and $code eq '4' ) {
- $value = GetAuthorisedValueDesc( $field->tag(), $code, $value, '', $tagslib );
- $linkvalue = "($value)";
- }
- # if no authority link, build a search query
- unless ($subfield9) {
- push @link_loop, {
- limit => 'au',
- 'link' => $linkvalue,
- operator => (scalar @link_loop) ? ' and ' : undef
- };
- }
- my @this_link_loop = @link_loop;
- # do not display $0
- unless ( $code eq '0') {
- push @subfields_loop, {
- tag => $field->tag(),
- code => $code,
- value => $value,
- link_loop => \@this_link_loop,
- separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
- };
- }
- }
- push @marcauthors, {
- MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop,
- authoritylink => $subfield9,
- unimarc3 => $unimarc3
- };
- }
- return \@marcauthors;
-}
-
=head2 GetMarcUrls
$marcurls = GetMarcUrls($record,$marcflavour);
$mintag = "225";
$maxtag = "225";
$fields_filter = '2..';
- } else { # marc21/normarc
+ } else { # marc21
$mintag = "440";
$maxtag = "490";
$fields_filter = '4..';
push @link_loop, {
'link' => $linkvalue,
- operator => (scalar @link_loop) ? ' and ' : undef
+ operator => (scalar @link_loop) ? ' AND ' : undef
};
if ($volume_number) {
return \@marcseries;
} #end getMARCseriess
-=head2 GetMarcHosts
-
- $marchostsarray = GetMarcHosts($record,$marcflavour);
-
-Get all host records (773s MARC21, 461 UNIMARC) from the MARC record and returns them in an array.
-
-=cut
-
-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" ) {
- $tag = "773";
- $title_subf = "t";
- $bibnumber_subf ="0";
- $itemnumber_subf='9';
- }
- elsif ($marcflavour eq "UNIMARC") {
- $tag = "461";
- $title_subf = "t";
- $bibnumber_subf ="0";
- $itemnumber_subf='9';
- };
-
- my @marchosts;
-
- foreach my $field ( $record->field($tag)) {
-
- my @fields_loop;
-
- my $hostbiblionumber = $field->subfield("$bibnumber_subf");
- my $hosttitle = $field->subfield($title_subf);
- my $hostitemnumber=$field->subfield($itemnumber_subf);
- push @fields_loop, { hostbiblionumber => $hostbiblionumber, hosttitle => $hosttitle, hostitemnumber => $hostitemnumber};
- push @marchosts, { MARCHOSTS_FIELDS_LOOP => \@fields_loop };
-
- }
- my $marchostsarray = \@marchosts;
- return $marchostsarray;
-}
-
=head2 UpsertMarcSubfield
my $record = C4::Biblio::UpsertMarcSubfield($MARC::Record, $fieldTag, $subfieldCode, $subfieldContent);
# In the next call we use the Default framework, since it is considered
# authoritative for Koha to Marc mappings.
- my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # do not change framewok
+ my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # do not change framework
my $tag_hr = {};
while ( my ($kohafield, $value) = each %$hash ) {
foreach my $fld ( @{ $mss->{$kohafield} } ) {
my $tagfield = $fld->{tagfield};
my $tagsubfield = $fld->{tagsubfield};
next if !$tagfield;
- my @values = $params->{no_split}
- ? ( $value )
- : split(/\s?\|\s?/, $value, -1);
+
+ # BZ 21800: split value if field is repeatable.
+ my @values = _check_split($params, $fld, $value)
+ ? split(/\s?\|\s?/, $value, -1)
+ : ( $value );
foreach my $value ( @values ) {
next if $value eq '';
$tag_hr->{$tagfield} //= [];
return $record;
}
+sub _check_split {
+# Checks if $value must be split; may consult passed framework
+ my ($params, $fld, $value) = @_;
+ return if index($value,'|') == -1; # nothing to worry about
+ return if $params->{no_split};
+
+ # if we did not get a specific framework, check default in $mss
+ return $fld->{repeatable} if !$params->{framework};
+
+ # here we need to check the specific framework
+ my $mss = GetMarcSubfieldStructure($params->{framework}, { unsafe => 1 });
+ foreach my $fld2 ( @{ $mss->{ $fld->{kohafield} } } ) {
+ next if $fld2->{tagfield} ne $fld->{tagfield};
+ next if $fld2->{tagsubfield} ne $fld->{tagsubfield};
+ return 1 if $fld2->{repeatable};
+ }
+ return;
+}
+
=head2 PrepHostMarcField
$hostfield = PrepHostMarcField ( $hostbiblionumber,$hostitemnumber,$marcflavour )
sub PrepHostMarcField {
my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
$marcflavour ||="MARC21";
-
- my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
+
+ my $biblio = Koha::Biblios->find($hostbiblionumber);
+ my $hostrecord = $biblio->metadata->record;
my $item = Koha::Items->find($hostitemnumber);
my $hostmarcfield;
- if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
+ if ( $marcflavour eq "MARC21" ) {
#main entry
my $mainentry;
my ( $tags, $subfields, $values, $indicator, $ind_tag, $auth_type ) = @_;
# NOTE: The parameter $ind_tag is NOT USED -- BZ 11247
+ my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
+
my $xml = MARC::File::XML::header('UTF-8');
$xml .= "<record>\n";
- $auth_type = C4::Context->preference('marcflavour') unless $auth_type;
+ $auth_type = C4::Context->preference('marcflavour') unless $auth_type; # FIXME auth_type must be removed
MARC::File::XML->default_record_format($auth_type);
# in UNIMARC, field 100 contains the encoding
# MARC::Record->new_from_xml will fail (and Koha will die)
my $unimarc_and_100_exist = 0;
$unimarc_and_100_exist = 1 if $auth_type eq 'ITEM'; # if we rebuild an item, no need of a 100 field
- my $prevvalue;
my $prevtag = -1;
my $first = 1;
my $j = -1;
my $close_last_tag;
for ( my $i = 0 ; $i < @$tags ; $i++ ) {
-
if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and @$tags[$i] eq "100" and @$subfields[$i] eq "a" ) {
# if we have a 100 field and it's values are not correct, skip them.
@$values[$i] =~ s/"/"/g;
@$values[$i] =~ s/'/'/g;
+ my $skip = @$values[$i] eq q{};
+ $skip = 0
+ if $perm_loc_tag
+ && $perm_loc_subfield
+ && @$tags[$i] eq $perm_loc_tag
+ && @$subfields[$i] eq $perm_loc_subfield;
+
if ( ( @$tags[$i] ne $prevtag ) ) {
$close_last_tag = 0;
$j++ unless ( @$tags[$i] eq "" );
if ( !$first ) {
$xml .= "</datafield>\n";
if ( ( @$tags[$i] && @$tags[$i] > 10 )
- && ( @$values[$i] ne "" ) ) {
+ && ( !$skip ) ) {
$xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
$xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
$first = 0;
$first = 1;
}
} else {
- if ( @$values[$i] ne "" ) {
+ if ( !$skip ) {
# leader
if ( @$tags[$i] eq "000" ) {
}
}
} else { # @$tags[$i] eq $prevtag
- if ( @$values[$i] eq "" ) {
- } else {
+ if ( !$skip ) {
if ($first) {
my $str = ( $indicator->[$j] // q{} ) . ' '; # extra space prevents substr outside of string warn
my $ind1 = _default_ind_to_space( substr( $str, 0, 1 ) );
}
}
+ @fields = sort { $a->tag() cmp $b->tag() } @fields;
$record->append_fields(@fields);
return $record;
}
=head2 TransformMarcToKoha
- $result = TransformMarcToKoha( $record, undef, $limit )
+ $result = TransformMarcToKoha({ record => $record, limit_table => $limit })
Extract data from a MARC bib record into a hashref representing
Koha biblio, biblioitems, and items fields.
=cut
sub TransformMarcToKoha {
- my ( $record, $frameworkcode, $limit_table ) = @_;
- # FIXME Parameter $frameworkcode is obsolete and will be removed
- $limit_table //= q{};
+ my ( $params ) = @_;
+
+ my $record = $params->{record};
+ my $limit_table = $params->{limit_table} // q{};
+ my $kohafields = $params->{kohafields};
my $result = {};
if (!defined $record) {
my %tables = ( biblio => 1, biblioitems => 1, items => 1 );
if( $limit_table eq 'items' ) {
%tables = ( items => 1 );
+ } elsif ( $limit_table eq 'no_items' ){
+ %tables = ( biblio => 1, biblioitems => 1 );
}
# The next call acknowledges Default as the authoritative framework
# for Koha to MARC mappings.
my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
- foreach my $kohafield ( keys %{ $mss } ) {
+ @{$kohafields} = keys %{ $mss } unless $kohafields;
+ foreach my $kohafield ( @{$kohafields} ) {
my ( $table, $column ) = split /[.]/, $kohafield, 2;
next unless $tables{$table};
- my $val = TransformMarcToKohaOneField( $kohafield, $record );
- next if !defined $val;
+ my ( $value, @values );
+ foreach my $fldhash ( @{$mss->{$kohafield}} ) {
+ my $tag = $fldhash->{tagfield};
+ my $sub = $fldhash->{tagsubfield};
+ foreach my $fld ( $record->field($tag) ) {
+ if( $sub eq '@' || $fld->is_control_field ) {
+ push @values, $fld->data if $fld->data;
+ } else {
+ push @values, grep { $_ } $fld->subfield($sub);
+ }
+ }
+ }
+ if ( @values ){
+ $value = join ' | ', uniq(@values);
+
+ # Additional polishing for individual kohafields
+ if( $kohafield =~ /copyrightdate|publicationyear/ ) {
+ $value = _adjust_pubyear( $value );
+ }
+ }
+
+ next if !defined $value;
my $key = _disambiguate( $table, $column );
- $result->{$key} = $val;
+ $result->{$key} = $value;
}
return $result;
}
}
-=head2 TransformMarcToKohaOneField
-
- $val = TransformMarcToKohaOneField( 'biblio.title', $marc );
-
- Note: The authoritative Default framework is used implicitly.
-
-=cut
-
-sub TransformMarcToKohaOneField {
- my ( $kohafield, $marc ) = @_;
-
- my ( @rv, $retval );
- my @mss = GetMarcSubfieldStructureFromKohaField($kohafield);
- foreach my $fldhash ( @mss ) {
- my $tag = $fldhash->{tagfield};
- my $sub = $fldhash->{tagsubfield};
- foreach my $fld ( $marc->field($tag) ) {
- if( $sub eq '@' || $fld->is_control_field ) {
- push @rv, $fld->data if $fld->data;
- } else {
- push @rv, grep { $_ } $fld->subfield($sub);
- }
- }
- }
- return unless @rv;
- $retval = join ' | ', uniq(@rv);
-
- # Additional polishing for individual kohafields
- if( $kohafield =~ /copyrightdate|publicationyear/ ) {
- $retval = _adjust_pubyear( $retval );
- }
-
- return $retval;
-}
-
=head2 _adjust_pubyear
- Helper routine for TransformMarcToKohaOneField
+ Helper routine for TransformMarcToKoha
=cut
$retval = $1;
} elsif( $retval =~ m/(\d\d\d\d)/ && $1 > 0 ) {
$retval = $1;
- } elsif( $retval =~ m/
- (?<year>\d)[-]?[.Xx?]{3}
- |(?<year>\d{2})[.Xx?]{2}
- |(?<year>\d{3})[.Xx?]
- |(?<year>\d)[-]{3}\?
- |(?<year>\d\d)[-]{2}\?
- |(?<year>\d{3})[-]\?
- /xms ) { # the form 198-? occurred in Dutch ISBD rules
- my $digits = $+{year};
- $retval = $digits * ( 10 ** ( 4 - length($digits) ));
+ } elsif( $retval =~ m/(?<year>\d{1,3})[.Xx?-]/ ) {
+ # See also bug 24674: enough to look at one unknown year char like .Xx-?
+ # At this point in code 1234? or 1234- already passed the earlier regex
+ # Things like 2-, 1xx, 1??? are now converted to a four positions-year.
+ $retval = $+{year} * ( 10 ** (4-length($+{year})) );
+ } else {
+ $retval = undef;
}
return $retval;
}
=head2 ModZebra
- ModZebra( $biblionumber, $op, $server, $record );
-
-$biblionumber is the biblionumber we want to index
+ ModZebra( $record_number, $op, $server );
-$op is specialUpdate or recordDelete, and is used to know what we want to do
+$record_number is the authid or biblionumber we want to index
-$server is the server that we want to update
+$op is the operation: specialUpdate or recordDelete
-$record is the update MARC record if it's available. If it's not supplied
-and is needed, it'll be loaded from the database.
+$server is authorityserver or biblioserver
=cut
sub ModZebra {
-###Accepts a $server variable thus we can use it for biblios authorities or other zebra dbs
- my ( $biblionumber, $op, $server, $record ) = @_;
- $debug && warn "ModZebra: update requested for: $biblionumber $op $server\n";
- if ( C4::Context->preference('SearchEngine') eq 'Elasticsearch' ) {
-
- # TODO abstract to a standard API that'll work for whatever
- require Koha::SearchEngine::Elasticsearch::Indexer;
- my $indexer = Koha::SearchEngine::Elasticsearch::Indexer->new(
- {
- index => $server eq 'biblioserver'
- ? $Koha::SearchEngine::BIBLIOS_INDEX
- : $Koha::SearchEngine::AUTHORITIES_INDEX
- }
- );
- if ( $op eq 'specialUpdate' ) {
- unless ($record) {
- $record = GetMarcBiblio({
- biblionumber => $biblionumber,
- embed_items => 1 });
- }
- my $records = [$record];
- $indexer->update_index_background( [$biblionumber], [$record] );
- }
- elsif ( $op eq 'recordDelete' ) {
- $indexer->delete_index_background( [$biblionumber] );
- }
- else {
- croak "ModZebra called with unknown operation: $op";
- }
- }
-
+ my ( $record_number, $op, $server ) = @_;
+ Koha::Logger->get->debug("ModZebra: updates requested for: $record_number $op $server");
my $dbh = C4::Context->dbh;
# true ModZebra commented until indexdata fixes zebraDB crashes (it seems they occur on multiple updates
AND operation = ?
AND done = 0";
my $check_sth = $dbh->prepare_cached($check_sql);
- $check_sth->execute( $server, $biblionumber, $op );
+ $check_sth->execute( $server, $record_number, $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->execute( $record_number, $server, $op );
$sth->finish;
}
}
-
-=head2 EmbedItemsInMarcBiblio
-
- EmbedItemsInMarcBiblio({
- marc_record => $marc,
- biblionumber => $biblionumber,
- item_numbers => $itemnumbers,
- opac => $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 $opac is true, then opac-relevant suppressions are included.
-
-If opac filtering will be done, borcat should be passed to properly
-override if necessary.
-
-=cut
-
-sub EmbedItemsInMarcBiblio {
- my ($params) = @_;
- my ($marc, $biblionumber, $itemnumbers, $opac, $borcat);
- $marc = $params->{marc_record};
- if ( !$marc ) {
- carp 'EmbedItemsInMarcBiblio: No MARC record passed';
- return;
- }
- $biblionumber = $params->{biblionumber};
- $itemnumbers = $params->{item_numbers};
- $opac = $params->{opac};
- $borcat = $params->{borcat} // q{};
-
- $itemnumbers = [] unless defined $itemnumbers;
-
- my $frameworkcode = GetFrameworkCode($biblionumber);
- _strip_item_fields($marc, $frameworkcode);
-
- # ... and embed the current items
- my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
- $sth->execute($biblionumber);
- my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber" );
-
- my @item_fields; # Array holding the actual MARC data for items to be included.
- my @items; # Array holding items which are both in the list (sitenumbers)
- # and on this biblionumber
-
- # Flag indicating if there is potential hiding.
- 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;
- my $item;
- if ( $opachiddenitems ) {
- $item = Koha::Items->find($itemnumber);
- $item = $item ? $item->unblessed : undef;
- }
- push @items, { itemnumber => $itemnumber, item => $item };
- }
- my @items2pass = map { $_->{item} } @items;
- my @hiddenitems =
- $opachiddenitems
- ? C4::Items::GetHiddenItemnumbers({
- items => \@items2pass,
- borcat => $borcat })
- : ();
- # 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);
-}
-
=head1 INTERNAL FUNCTIONS
=head2 _koha_marc_update_bib_ids
} else {
C4::Biblio::UpsertMarcSubfield($record, $biblioitem_tag, $biblioitem_subfield, $biblioitemnumber);
}
+
+ # update the control number (001) in MARC
+ if(C4::Context->preference('autoControlNumber') eq 'biblionumber'){
+ unless($record->field('001')){
+ $record->insert_fields_ordered(MARC::Field->new('001', $biblionumber));
+ }
+ }
}
=head2 _koha_marc_update_biblioitem_cn_sort
}
}
-=head2 _koha_add_biblio
-
- my ($biblionumber,$error) = _koha_add_biblio($dbh,$biblioitem);
-
-Internal function to add a biblio ($biblio is a hash with the values)
-
-=cut
-
-sub _koha_add_biblio {
- my ( $dbh, $biblio, $frameworkcode ) = @_;
-
- my $error;
-
- # set the series flag
- unless (defined $biblio->{'serial'}){
- $biblio->{'serial'} = 0;
- if ( $biblio->{'seriestitle'} ) { $biblio->{'serial'} = 1 }
- }
-
- my $query = "INSERT INTO biblio
- SET frameworkcode = ?,
- author = ?,
- title = ?,
- subtitle = ?,
- medium = ?,
- part_number = ?,
- part_name = ?,
- unititle =?,
- notes = ?,
- serial = ?,
- seriestitle = ?,
- copyrightdate = ?,
- datecreated=NOW(),
- abstract = ?
- ";
- my $sth = $dbh->prepare($query);
- $sth->execute(
- $frameworkcode, $biblio->{'author'}, $biblio->{'title'}, $biblio->{'subtitle'},
- $biblio->{'medium'}, $biblio->{'part_number'}, $biblio->{'part_name'}, $biblio->{'unititle'},
- $biblio->{'notes'}, $biblio->{'serial'}, $biblio->{'seriestitle'}, $biblio->{'copyrightdate'},
- $biblio->{'abstract'}
- );
-
- my $biblionumber = $dbh->{'mysql_insertid'};
- if ( $dbh->errstr ) {
- $error .= "ERROR in _koha_add_biblio $query" . $dbh->errstr;
- warn $error;
- }
-
- $sth->finish();
-
- #warn "LEAVING _koha_add_biblio: ".$biblionumber."\n";
- return ( $biblionumber, $error );
-}
-
=head2 _koha_modify_biblio
my ($biblionumber,$error) == _koha_modify_biblio($dbh,$biblio,$frameworkcode);
return ( $biblioitem->{'biblioitemnumber'}, $error );
}
-=head2 _koha_add_biblioitem
-
- my ($biblioitemnumber,$error) = _koha_add_biblioitem( $dbh, $biblioitem );
-
-Internal function to add a biblioitem
-
-=cut
-
-sub _koha_add_biblioitem {
- my ( $dbh, $biblioitem ) = @_;
- my $error;
-
- my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
- my $query = "INSERT INTO biblioitems SET
- biblionumber = ?,
- volume = ?,
- number = ?,
- itemtype = ?,
- isbn = ?,
- issn = ?,
- publicationyear = ?,
- publishercode = ?,
- volumedate = ?,
- volumedesc = ?,
- collectiontitle = ?,
- collectionissn = ?,
- collectionvolume= ?,
- editionstatement= ?,
- editionresponsibility = ?,
- illus = ?,
- pages = ?,
- notes = ?,
- size = ?,
- place = ?,
- lccn = ?,
- url = ?,
- cn_source = ?,
- cn_class = ?,
- cn_item = ?,
- cn_suffix = ?,
- cn_sort = ?,
- totalissues = ?,
- ean = ?,
- agerestriction = ?
- ";
- my $sth = $dbh->prepare($query);
- $sth->execute(
- $biblioitem->{'biblionumber'}, $biblioitem->{'volume'}, $biblioitem->{'number'}, $biblioitem->{'itemtype'},
- $biblioitem->{'isbn'}, $biblioitem->{'issn'}, $biblioitem->{'publicationyear'}, $biblioitem->{'publishercode'},
- $biblioitem->{'volumedate'}, $biblioitem->{'volumedesc'}, $biblioitem->{'collectiontitle'}, $biblioitem->{'collectionissn'},
- $biblioitem->{'collectionvolume'}, $biblioitem->{'editionstatement'}, $biblioitem->{'editionresponsibility'}, $biblioitem->{'illus'},
- $biblioitem->{'pages'}, $biblioitem->{'bnotes'}, $biblioitem->{'size'}, $biblioitem->{'place'},
- $biblioitem->{'lccn'}, $biblioitem->{'url'}, $biblioitem->{'biblioitems.cn_source'},
- $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'}, $biblioitem->{'cn_suffix'}, $cn_sort,
- $biblioitem->{'totalissues'}, $biblioitem->{'ean'}, $biblioitem->{'agerestriction'}
- );
- my $bibitemnum = $dbh->{'mysql_insertid'};
-
- if ( $dbh->errstr ) {
- $error .= "ERROR in _koha_add_biblioitem $query" . $dbh->errstr;
- warn $error;
- }
- $sth->finish();
- return ( $bibitemnum, $error );
-}
-
=head2 _koha_delete_biblio
$error = _koha_delete_biblio($dbh,$biblionumber);
=head2 ModBiblioMarc
- &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
+ ModBiblioMarc($newrec,$biblionumber);
Add MARC XML data for a biblio to koha
sub ModBiblioMarc {
# pass the MARC::Record to this function, and it will create the records in
# the marcxml field
- my ( $record, $biblionumber, $frameworkcode ) = @_;
+ my ( $record, $biblionumber, $params ) = @_;
if ( !$record ) {
carp 'ModBiblioMarc passed an undefined record';
return;
}
+ my $skip_record_index = $params->{skip_record_index} || 0;
+
# Clone record as it gets modified
$record = $record->clone();
my $dbh = C4::Context->dbh;
my @fields = $record->fields();
- if ( !$frameworkcode ) {
- $frameworkcode = "";
- }
- my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
- $sth->execute( $frameworkcode, $biblionumber );
- $sth->finish;
my $encoding = C4::Context->preference("marcflavour");
# deal with UNIMARC field 100 (encoding) : create it if needed & set encoding to unicode
$m_rs->metadata( $record->as_xml_record($encoding) );
$m_rs->store;
- ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
+ unless ( $skip_record_index ) {
+ my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+ $indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" );
+ }
return $biblionumber;
}
sub prepare_host_field {
my ( $hostbiblio, $marcflavour ) = @_;
$marcflavour ||= C4::Context->preference('marcflavour');
- my $host = GetMarcBiblio({ biblionumber => $hostbiblio });
+
+ my $biblio = Koha::Biblios->find($hostbiblio);
+ my $host = $biblio->metadata->record;
# 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 ( $marcflavour eq 'MARC21' ) {
if ( $field = $host->field('100') || $host->field('110') || $host->field('11') ) {
my $s = $field->as_string('ab');
if ($s) {
=cut
sub UpdateTotalIssues {
- my ($biblionumber, $increase, $value) = @_;
+ my ($biblionumber, $increase, $value, $skip_holds_queue) = @_;
my $totalissues;
- my $record = GetMarcBiblio({ biblionumber => $biblionumber });
- unless ($record) {
- carp "UpdateTotalIssues could not get biblio record";
+ my $biblio = Koha::Biblios->find($biblionumber);
+ unless ($biblio) {
+ carp "UpdateTotalIssues could not get biblio";
return;
}
- my $biblio = Koha::Biblios->find( $biblionumber );
- unless ($biblio) {
- carp "UpdateTotalIssues could not get datas of biblio";
+
+ my $record = $biblio->metadata->record;
+ unless ($record) {
+ carp "UpdateTotalIssues could not get biblio record";
return;
}
my $biblioitem = $biblio->biblioitem;
$record->insert_grouped_field($field);
}
- return ModBiblio($record, $biblionumber, $biblio->frameworkcode);
+ return ModBiblio($record, $biblionumber, $biblio->frameworkcode, { skip_holds_queue => $skip_holds_queue });
}
=head2 RemoveAllNsb
return $record;
}
-1;
+=head2 ApplyMarcOverlayRules
+
+ my $record = ApplyMarcOverlayRules($params)
+
+Applies marc merge rules to a record.
+
+C<$params> is expected to be a hashref with below keys defined.
+
+=over 4
+
+=item C<biblionumber>
+biblionumber of old record
+
+=item C<record>
+Incoming record that will be merged with old record
+
+=item C<overlay_context>
+hashref containing at least one context module and filter value on
+the form {module => filter, ...}.
+
+=back
+
+Returns:
+
+=over 4
+
+=item C<$record>
+
+Merged MARC record based with merge rules for C<context> applied. If no old
+record for C<biblionumber> can be found, C<record> is returned unchanged.
+Default action when no matching context is found to return C<record> unchanged.
+If no rules are found for a certain field tag the default is to overwrite with
+fields with this field tag from C<record>.
+
+=back
+
+=cut
+sub ApplyMarcOverlayRules {
+ my ($params) = @_;
+ my $biblionumber = $params->{biblionumber};
+ my $incoming_record = $params->{record};
+
+ if (!$biblionumber) {
+ carp 'ApplyMarcOverlayRules called on undefined biblionumber';
+ return;
+ }
+ if (!$incoming_record) {
+ carp 'ApplyMarcOverlayRules called on undefined record';
+ return;
+ }
+ my $biblio = Koha::Biblios->find($biblionumber);
+ my $old_record = $biblio->metadata->record;
+
+ # Skip overlay rules if called with no context
+ if ($old_record && defined $params->{overlay_context}) {
+ return Koha::MarcOverlayRules->merge_records($old_record, $incoming_record, $params->{overlay_context});
+ }
+ return $incoming_record;
+}
=head2 _after_biblio_action_hooks
my $biblio_id = $args->{biblio_id};
my $action = $args->{action};
- if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
-
- my @plugins = Koha::Plugins->new->GetPlugins({
- method => 'after_biblio_action',
- });
-
- if (@plugins) {
-
- my $biblio = Koha::Biblios->find( $biblio_id );
-
- foreach my $plugin ( @plugins ) {
- try {
- $plugin->after_biblio_action({ action => $action, biblio => $biblio, biblio_id => $biblio_id });
- }
- catch {
- warn "$_";
- };
- }
+ my $biblio = Koha::Biblios->find( $biblio_id );
+ Koha::Plugins->call(
+ 'after_biblio_action',
+ {
+ action => $action,
+ biblio => $biblio,
+ biblio_id => $biblio_id,
}
- }
+ );
}
+1;
+
__END__
=head1 AUTHOR