3 # Copyright 2000-2002 Katipo Communications
4 # Copyright 2010 BibLibre
5 # Copyright 2011 Equinox Software, Inc.
7 # This file is part of Koha.
9 # Koha is free software; you can redistribute it and/or modify it
10 # under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # Koha is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with Koha; if not, see <http://www.gnu.org/licenses>.
24 use vars qw(@ISA @EXPORT);
50 GetAuthorisedValueDesc
52 IsMarcStructureInternal
54 GetMarcSubfieldStructureFromKohaField
66 LinkBibHeadingsToAuthorities
74 # those functions are exported but should not be used
75 # they are useful in a few circumstances, so they are exported,
76 # but don't use them unless you are a core developer ;-)
84 use Encode qw( decode is_utf8 );
85 use List::MoreUtils qw( uniq );
87 use MARC::File::USMARC;
89 use POSIX qw(strftime);
90 use Module::Load::Conditional qw(can_load);
93 use C4::Log; # logaction
102 use Koha::Authority::Types;
103 use Koha::Acquisition::Currencies;
104 use Koha::Biblio::Metadata;
105 use Koha::Biblio::Metadatas;
108 use Koha::SearchEngine;
111 use vars qw($debug $cgi_debug);
116 C4::Biblio - cataloging management functions
120 Biblio.pm contains functions for managing storage and editing of bibliographic data within Koha. Most of the functions in this module are used for cataloging records: adding, editing, or removing biblios, biblioitems, or items. Koha's stores bibliographic information in three places:
124 =item 1. in the biblio,biblioitems,items, etc tables, which are limited to a one-to-one mapping to underlying MARC data
126 =item 2. as raw MARC in the Zebra index and storage engine
128 =item 3. as MARC XML in biblio_metadata.metadata
132 In the 3.0 version of Koha, the authoritative record-level information is in biblio_metadata.metadata
134 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.
138 =item 1. Compared with MySQL, Zebra is slow to update an index for small data changes -- especially for proc-intensive operations like circulation
140 =item 2. Zebra's index has been known to crash and a backup of the data is necessary to rebuild it in such cases
144 Because of this design choice, the process of managing storage and editing is a bit convoluted. Historically, Biblio.pm's grown to an unmanagable size and as a result we have several types of functions currently:
148 =item 1. Add*/Mod*/Del*/ - high-level external functions suitable for being called from external scripts to manage the collection
150 =item 2. _koha_* - low-level internal functions for managing the koha tables
152 =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.
154 =item 4. Zebra functions used to update the Zebra index
156 =item 5. internal helper functions such as char_decode, checkitems, etc. Some of these probably belong in Koha.pm
160 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 :
164 =item 1. save datas in biblio and biblioitems table, that gives us a biblionumber and a biblioitemnumber
166 =item 2. add the biblionumber and biblioitemnumber into the MARC records
168 =item 3. save the marc record
172 =head1 EXPORTED FUNCTIONS
176 ($biblionumber,$biblioitemnumber) = AddBiblio($record,$frameworkcode);
178 Exported function (core API) for adding a new biblio to koha.
180 The first argument is a C<MARC::Record> object containing the
181 bib to add, while the second argument is the desired MARC
184 This function also accepts a third, optional argument: a hashref
185 to additional options. The only defined option is C<defer_marc_save>,
186 which if present and mapped to a true value, causes C<AddBiblio>
187 to omit the call to save the MARC in C<biblio_metadata.metadata>
188 This option is provided B<only>
189 for the use of scripts such as C<bulkmarcimport.pl> that may need
190 to do some manipulation of the MARC record for item parsing before
191 saving it and which cannot afford the performance hit of saving
192 the MARC record twice. Consequently, do not use that option
193 unless you can guarantee that C<ModBiblioMarc> will be called.
199 my $frameworkcode = shift;
200 my $options = @_ ? shift : undef;
201 my $defer_marc_save = 0;
203 carp('AddBiblio called with undefined record');
206 if ( defined $options and exists $options->{'defer_marc_save'} and $options->{'defer_marc_save'} ) {
207 $defer_marc_save = 1;
210 my ( $biblionumber, $biblioitemnumber, $error );
211 my $dbh = C4::Context->dbh;
213 # transform the data into koha-table style data
214 SetUTF8Flag($record);
215 my $olddata = TransformMarcToKoha( $record, $frameworkcode );
216 ( $biblionumber, $error ) = _koha_add_biblio( $dbh, $olddata, $frameworkcode );
217 $olddata->{'biblionumber'} = $biblionumber;
218 ( $biblioitemnumber, $error ) = _koha_add_biblioitem( $dbh, $olddata );
220 _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
222 # update MARC subfield that stores biblioitems.cn_sort
223 _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
226 ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
228 # update OAI-PMH sets
229 if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
230 C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
233 logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
234 return ( $biblionumber, $biblioitemnumber );
239 ModBiblio( $record,$biblionumber,$frameworkcode);
241 Replace an existing bib record identified by C<$biblionumber>
242 with one supplied by the MARC::Record object C<$record>. The embedded
243 item, biblioitem, and biblionumber fields from the previous
244 version of the bib record replace any such fields of those tags that
245 are present in C<$record>. Consequently, ModBiblio() is not
246 to be used to try to modify item records.
248 C<$frameworkcode> specifies the MARC framework to use
249 when storing the modified bib record; among other things,
250 this controls how MARC fields get mapped to display columns
251 in the C<biblio> and C<biblioitems> tables, as well as
252 which fields are used to store embedded item, biblioitem,
253 and biblionumber data for indexing.
255 Returns 1 on success 0 on failure
260 my ( $record, $biblionumber, $frameworkcode ) = @_;
262 carp 'No record passed to ModBiblio';
266 if ( C4::Context->preference("CataloguingLog") ) {
267 my $newrecord = GetMarcBiblio({ biblionumber => $biblionumber });
268 logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
271 # Cleaning up invalid fields must be done early or SetUTF8Flag is liable to
272 # throw an exception which probably won't be handled.
273 foreach my $field ($record->fields()) {
274 if (! $field->is_control_field()) {
275 if (scalar($field->subfields()) == 0 || (scalar($field->subfields()) == 1 && $field->subfield('9'))) {
276 $record->delete_field($field);
281 SetUTF8Flag($record);
282 my $dbh = C4::Context->dbh;
284 $frameworkcode = "" if !$frameworkcode || $frameworkcode eq "Default"; # XXX
286 _strip_item_fields($record, $frameworkcode);
288 # update biblionumber and biblioitemnumber in MARC
289 # FIXME - this is assuming a 1 to 1 relationship between
290 # biblios and biblioitems
291 my $sth = $dbh->prepare("select biblioitemnumber from biblioitems where biblionumber=?");
292 $sth->execute($biblionumber);
293 my ($biblioitemnumber) = $sth->fetchrow;
295 _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
297 # load the koha-table data object
298 my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
300 # update MARC subfield that stores biblioitems.cn_sort
301 _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
303 # update the MARC record (that now contains biblio and items) with the new record data
304 &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
306 # modify the other koha tables
307 _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
308 _koha_modify_biblioitem_nonmarc( $dbh, $oldbiblio );
310 # update OAI-PMH sets
311 if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
312 C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
318 =head2 _strip_item_fields
320 _strip_item_fields($record, $frameworkcode)
322 Utility routine to remove item tags from a
327 sub _strip_item_fields {
329 my $frameworkcode = shift;
330 # get the items before and append them to the biblio before updating the record, atm we just have the biblio
331 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
333 # delete any item fields from incoming record to avoid
334 # duplication or incorrect data - use AddItem() or ModItem()
336 foreach my $field ( $record->field($itemtag) ) {
337 $record->delete_field($field);
343 my $error = &DelBiblio($biblionumber);
345 Exported function (core API) for deleting a biblio in koha.
346 Deletes biblio record from Zebra and Koha tables (biblio & biblioitems)
347 Also backs it up to deleted* tables.
348 Checks to make sure that the biblio has no items attached.
350 C<$error> : undef unless an error occurs
355 my ($biblionumber) = @_;
356 my $dbh = C4::Context->dbh;
357 my $error; # for error handling
359 # First make sure this biblio has no items attached
360 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber=?");
361 $sth->execute($biblionumber);
362 if ( my $itemnumber = $sth->fetchrow ) {
364 # Fix this to use a status the template can understand
365 $error .= "This Biblio has items attached, please delete them first before deleting this biblio ";
368 return $error if $error;
370 # We delete attached subscriptions
372 my $subscriptions = C4::Serials::GetFullSubscriptionsFromBiblionumber($biblionumber);
373 foreach my $subscription (@$subscriptions) {
374 C4::Serials::DelSubscription( $subscription->{subscriptionid} );
377 # We delete any existing holds
378 my $biblio = Koha::Biblios->find( $biblionumber );
379 my $holds = $biblio->holds;
380 while ( my $hold = $holds->next ) {
384 # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
385 # for at least 2 reasons :
386 # - if something goes wrong, the biblio may be deleted from Koha but not from zebra
387 # 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)
388 ModZebra( $biblionumber, "recordDelete", "biblioserver" );
390 # delete biblioitems and items from Koha tables and save in deletedbiblioitems,deleteditems
391 $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
392 $sth->execute($biblionumber);
393 while ( my $biblioitemnumber = $sth->fetchrow ) {
395 # delete this biblioitem
396 $error = _koha_delete_biblioitems( $dbh, $biblioitemnumber );
397 return $error if $error;
401 # delete biblio from Koha tables and save in deletedbiblio
402 # must do this *after* _koha_delete_biblioitems, otherwise
403 # delete cascade will prevent deletedbiblioitems rows
404 # from being generated by _koha_delete_biblioitems
405 $error = _koha_delete_biblio( $dbh, $biblionumber );
407 logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
413 =head2 BiblioAutoLink
415 my $headings_linked = BiblioAutoLink($record, $frameworkcode)
417 Automatically links headings in a bib record to authorities.
419 Returns the number of headings changed
425 my $frameworkcode = shift;
427 carp('Undefined record passed to BiblioAutoLink');
430 my ( $num_headings_changed, %results );
433 "C4::Linker::" . ( C4::Context->preference("LinkerModule") || 'Default' );
434 unless ( can_load( modules => { $linker_module => undef } ) ) {
435 $linker_module = 'C4::Linker::Default';
436 unless ( can_load( modules => { $linker_module => undef } ) ) {
441 my $linker = $linker_module->new(
442 { 'options' => C4::Context->preference("LinkerOptions") } );
443 my ( $headings_changed, undef ) =
444 LinkBibHeadingsToAuthorities( $linker, $record, $frameworkcode, C4::Context->preference("CatalogModuleRelink") || '' );
445 # By default we probably don't want to relink things when cataloging
446 return $headings_changed;
449 =head2 LinkBibHeadingsToAuthorities
451 my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink]);
453 Links bib headings to authority records by checking
454 each authority-controlled field in the C<MARC::Record>
455 object C<$marc>, looking for a matching authority record,
456 and setting the linking subfield $9 to the ID of that
459 If $allowrelink is false, existing authids will never be
460 replaced, regardless of the values of LinkerKeepStale and
463 Returns the number of heading links changed in the
468 sub LinkBibHeadingsToAuthorities {
471 my $frameworkcode = shift;
472 my $allowrelink = shift;
475 carp 'LinkBibHeadingsToAuthorities called on undefined bib record';
479 require C4::AuthoritiesMarc;
481 $allowrelink = 1 unless defined $allowrelink;
482 my $num_headings_changed = 0;
483 foreach my $field ( $bib->fields() ) {
484 my $heading = C4::Heading->new_from_bib_field( $field, $frameworkcode );
485 next unless defined $heading;
488 my $current_link = $field->subfield('9');
490 if ( defined $current_link && (!$allowrelink || !C4::Context->preference('LinkerRelink')) )
492 $results{'linked'}->{ $heading->display_form() }++;
496 my ( $authid, $fuzzy ) = $linker->get_link($heading);
498 $results{ $fuzzy ? 'fuzzy' : 'linked' }
499 ->{ $heading->display_form() }++;
500 next if defined $current_link and $current_link == $authid;
502 $field->delete_subfield( code => '9' ) if defined $current_link;
503 $field->add_subfields( '9', $authid );
504 $num_headings_changed++;
507 if ( defined $current_link
508 && (!$allowrelink || C4::Context->preference('LinkerKeepStale')) )
510 $results{'fuzzy'}->{ $heading->display_form() }++;
512 elsif ( C4::Context->preference('AutoCreateAuthorities') ) {
513 if ( _check_valid_auth_link( $current_link, $field ) ) {
514 $results{'linked'}->{ $heading->display_form() }++;
517 my $authority_type = Koha::Authority::Types->find( $heading->auth_type() );
518 my $marcrecordauth = MARC::Record->new();
519 if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
520 $marcrecordauth->leader(' nz a22 o 4500');
521 SetMarcUnicodeFlag( $marcrecordauth, 'MARC21' );
523 $field->delete_subfield( code => '9' )
524 if defined $current_link;
526 MARC::Field->new( $authority_type->auth_tag_to_report,
527 '', '', "a" => "" . $field->subfield('a') );
529 $authfield->add_subfields( $_->[0] => $_->[1] )
530 if ( $_->[0] =~ /[A-z]/ && $_->[0] ne "a" )
531 } $field->subfields();
532 $marcrecordauth->insert_fields_ordered($authfield);
534 # bug 2317: ensure new authority knows it's using UTF-8; currently
535 # only need to do this for MARC21, as MARC::Record->as_xml_record() handles
536 # automatically for UNIMARC (by not transcoding)
537 # FIXME: AddAuthority() instead should simply explicitly require that the MARC::Record
538 # use UTF-8, but as of 2008-08-05, did not want to introduce that kind
539 # of change to a core API just before the 3.0 release.
541 if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
542 my $userenv = C4::Context->userenv;
544 if ( $userenv && $userenv->{'branch'} ) {
545 $library = Koha::Libraries->find( $userenv->{'branch'} );
547 $marcrecordauth->insert_fields_ordered(
550 'a' => "Machine generated authority record."
554 $bib->author() . ", "
555 . $bib->title_proper() . ", "
556 . $bib->publication_date() . " ";
557 $cite =~ s/^[\s\,]*//;
558 $cite =~ s/[\s\,]*$//;
561 . ( $library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') ) . ")"
562 . $bib->subfield( '999', 'c' ) . ": "
564 $marcrecordauth->insert_fields_ordered(
565 MARC::Field->new( '670', '', '', 'a' => $cite ) );
568 # warn "AUTH RECORD ADDED : ".$marcrecordauth->as_formatted;
571 C4::AuthoritiesMarc::AddAuthority( $marcrecordauth, '',
572 $heading->auth_type() );
573 $field->add_subfields( '9', $authid );
574 $num_headings_changed++;
575 $linker->update_cache($heading, $authid);
576 $results{'added'}->{ $heading->display_form() }++;
579 elsif ( defined $current_link ) {
580 if ( _check_valid_auth_link( $current_link, $field ) ) {
581 $results{'linked'}->{ $heading->display_form() }++;
584 $field->delete_subfield( code => '9' );
585 $num_headings_changed++;
586 $results{'unlinked'}->{ $heading->display_form() }++;
590 $results{'unlinked'}->{ $heading->display_form() }++;
595 return $num_headings_changed, \%results;
598 =head2 _check_valid_auth_link
600 if ( _check_valid_auth_link($authid, $field) ) {
604 Check whether the specified heading-auth link is valid without reference
605 to Zebra. Ideally this code would be in C4::Heading, but that won't be
606 possible until we have de-cycled C4::AuthoritiesMarc, so this is the
611 sub _check_valid_auth_link {
612 my ( $authid, $field ) = @_;
614 require C4::AuthoritiesMarc;
616 my $authorized_heading =
617 C4::AuthoritiesMarc::GetAuthorizedHeading( { 'authid' => $authid } ) || '';
619 return ($field->as_string('abcdefghijklmnopqrstuvwxyz') eq $authorized_heading);
622 =head2 GetRecordValue
624 my $values = GetRecordValue($field, $record, $frameworkcode);
626 Get MARC fields from a keyword defined in fieldmapping table.
631 my ( $field, $record, $frameworkcode ) = @_;
634 carp 'GetRecordValue called with undefined record';
637 my $dbh = C4::Context->dbh;
639 my $sth = $dbh->prepare('SELECT fieldcode, subfieldcode FROM fieldmapping WHERE frameworkcode = ? AND field = ?');
640 $sth->execute( $frameworkcode, $field );
644 while ( my $row = $sth->fetchrow_hashref ) {
645 foreach my $field ( $record->field( $row->{fieldcode} ) ) {
646 if ( ( $row->{subfieldcode} ne "" && $field->subfield( $row->{subfieldcode} ) ) ) {
647 foreach my $subfield ( $field->subfield( $row->{subfieldcode} ) ) {
648 push @result, { 'subfield' => $subfield };
651 } elsif ( $row->{subfieldcode} eq "" ) {
652 push @result, { 'subfield' => $field->as_string() };
662 $data = &GetBiblioData($biblionumber);
664 Returns information about the book with the given biblionumber.
665 C<&GetBiblioData> returns a reference-to-hash. The keys are the fields in
666 the C<biblio> and C<biblioitems> tables in the
669 In addition, C<$data-E<gt>{subject}> is the list of the book's
670 subjects, separated by C<" , "> (space, comma, space).
671 If there are multiple biblioitems with the given biblionumber, only
672 the first one is considered.
678 my $dbh = C4::Context->dbh;
680 my $query = " SELECT * , biblioitems.notes AS bnotes, itemtypes.notforloan as bi_notforloan, biblio.notes
682 LEFT JOIN biblioitems ON biblio.biblionumber = biblioitems.biblionumber
683 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
684 WHERE biblio.biblionumber = ?";
686 my $sth = $dbh->prepare($query);
687 $sth->execute($bibnum);
689 $data = $sth->fetchrow_hashref;
693 } # sub GetBiblioData
697 $isbd = &GetISBDView({
698 'record' => $marc_record,
699 'template' => $interface, # opac/intranet
700 'framework' => $framework,
703 Return the ISBD view which can be included in opac and intranet
710 # Expecting record WITH items.
711 my $record = $params->{record};
712 return unless defined $record;
714 my $template = $params->{template} // q{};
715 my $sysprefname = $template eq 'opac' ? 'opacisbd' : 'isbd';
716 my $framework = $params->{framework};
717 my $itemtype = $framework;
718 my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch", $itemtype );
719 my $tagslib = GetMarcStructure( 1, $itemtype, { unsafe => 1 } );
721 my $ISBD = C4::Context->preference($sysprefname);
726 foreach my $isbdfield ( split( /#/, $bloc ) ) {
728 # $isbdfield= /(.?.?.?)/;
729 $isbdfield =~ /(\d\d\d)([^\|])?\|(.*)\|(.*)\|(.*)/;
730 my $fieldvalue = $1 || 0;
731 my $subfvalue = $2 || "";
733 my $analysestring = $4;
736 # warn "==> $1 / $2 / $3 / $4";
737 # my $fieldvalue=substr($isbdfield,0,3);
738 if ( $fieldvalue > 0 ) {
739 my $hasputtextbefore = 0;
740 my @fieldslist = $record->field($fieldvalue);
741 @fieldslist = sort { $a->subfield($holdingbrtagsubf) cmp $b->subfield($holdingbrtagsubf) } @fieldslist if ( $fieldvalue eq $holdingbrtagf );
743 # warn "ERROR IN ISBD DEFINITION at : $isbdfield" unless $fieldvalue;
744 # warn "FV : $fieldvalue";
745 if ( $subfvalue ne "" ) {
746 # OPAC hidden subfield
748 if ( ( $template eq 'opac' )
749 && ( $tagslib->{$fieldvalue}->{$subfvalue}->{'hidden'} || 0 ) > 0 );
750 foreach my $field (@fieldslist) {
751 foreach my $subfield ( $field->subfield($subfvalue) ) {
752 my $calculated = $analysestring;
753 my $tag = $field->tag();
756 my $subfieldvalue = GetAuthorisedValueDesc( $tag, $subfvalue, $subfield, '', $tagslib );
757 my $tagsubf = $tag . $subfvalue;
758 $calculated =~ s/\{(.?.?.?.?)$tagsubf(.*?)\}/$1$subfieldvalue$2\{$1$tagsubf$2\}/g;
759 if ( $template eq "opac" ) { $calculated =~ s#/cgi-bin/koha/[^/]+/([^.]*.pl\?.*)$#opac-$1#g; }
761 # field builded, store the result
762 if ( $calculated && !$hasputtextbefore ) { # put textbefore if not done
763 $blocres .= $textbefore;
764 $hasputtextbefore = 1;
767 # remove punctuation at start
768 $calculated =~ s/^( |;|:|\.|-)*//g;
769 $blocres .= $calculated;
774 $blocres .= $textafter if $hasputtextbefore;
776 foreach my $field (@fieldslist) {
777 my $calculated = $analysestring;
778 my $tag = $field->tag();
781 my @subf = $field->subfields;
782 for my $i ( 0 .. $#subf ) {
783 my $valuecode = $subf[$i][1];
784 my $subfieldcode = $subf[$i][0];
785 # OPAC hidden subfield
787 if ( ( $template eq 'opac' )
788 && ( $tagslib->{$fieldvalue}->{$subfieldcode}->{'hidden'} || 0 ) > 0 );
789 my $subfieldvalue = GetAuthorisedValueDesc( $tag, $subf[$i][0], $subf[$i][1], '', $tagslib );
790 my $tagsubf = $tag . $subfieldcode;
792 $calculated =~ s/ # replace all {{}} codes by the value code.
793 \{\{$tagsubf\}\} # catch the {{actualcode}}
795 $valuecode # replace by the value code
798 $calculated =~ s/\{(.?.?.?.?)$tagsubf(.*?)\}/$1$subfieldvalue$2\{$1$tagsubf$2\}/g;
799 if ( $template eq "opac" ) { $calculated =~ s#/cgi-bin/koha/[^/]+/([^.]*.pl\?.*)$#opac-$1#g; }
802 # field builded, store the result
803 if ( $calculated && !$hasputtextbefore ) { # put textbefore if not done
804 $blocres .= $textbefore;
805 $hasputtextbefore = 1;
808 # remove punctuation at start
809 $calculated =~ s/^( |;|:|\.|-)*//g;
810 $blocres .= $calculated;
813 $blocres .= $textafter if $hasputtextbefore;
816 $blocres .= $isbdfield;
821 $res =~ s/\{(.*?)\}//g;
823 $res =~ s/\n/<br\/>/g;
831 =head1 FUNCTIONS FOR HANDLING MARC MANAGEMENT
833 =head2 IsMarcStructureInternal
835 my $tagslib = C4::Biblio::GetMarcStructure();
836 for my $tag ( sort keys %$tagslib ) {
838 for my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
839 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
844 GetMarcStructure creates keys (lib, tab, mandatory, repeatable) for a display purpose.
845 These different values should not be processed as valid subfields.
849 sub IsMarcStructureInternal {
850 my ( $subfield ) = @_;
851 return ref $subfield ? 0 : 1;
854 =head2 GetMarcStructure
856 $res = GetMarcStructure($forlibrarian, $frameworkcode, [ $params ]);
858 Returns a reference to a big hash of hash, with the Marc structure for the given frameworkcode
859 $forlibrarian :if set to 1, the MARC descriptions are the librarians ones, otherwise it's the public (OPAC) ones
860 $frameworkcode : the framework code to read
861 $params allows you to pass { unsafe => 1 } for better performance.
863 Note: If you call GetMarcStructure with unsafe => 1, do not modify or
864 even autovivify its contents. It is a cached/shared data structure. Your
865 changes c/would be passed around in subsequent calls.
869 sub GetMarcStructure {
870 my ( $forlibrarian, $frameworkcode, $params ) = @_;
871 $frameworkcode = "" unless $frameworkcode;
873 $forlibrarian = $forlibrarian ? 1 : 0;
874 my $unsafe = ($params && $params->{unsafe})? 1: 0;
875 my $cache = Koha::Caches->get_instance();
876 my $cache_key = "MarcStructure-$forlibrarian-$frameworkcode";
877 my $cached = $cache->get_from_cache($cache_key, { unsafe => $unsafe });
878 return $cached if $cached;
880 my $dbh = C4::Context->dbh;
881 my $sth = $dbh->prepare(
882 "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable,ind1_defaultvalue,ind2_defaultvalue
883 FROM marc_tag_structure
884 WHERE frameworkcode=?
887 $sth->execute($frameworkcode);
888 my ( $liblibrarian, $libopac, $tag, $res, $tab, $mandatory, $repeatable, $ind1_defaultvalue, $ind2_defaultvalue );
890 while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable, $ind1_defaultvalue, $ind2_defaultvalue ) = $sth->fetchrow ) {
891 $res->{$tag}->{lib} = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
892 $res->{$tag}->{tab} = "";
893 $res->{$tag}->{mandatory} = $mandatory;
894 $res->{$tag}->{repeatable} = $repeatable;
895 $res->{$tag}->{ind1_defaultvalue} = $ind1_defaultvalue;
896 $res->{$tag}->{ind2_defaultvalue} = $ind2_defaultvalue;
899 $sth = $dbh->prepare(
900 "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab,mandatory,repeatable,authorised_value,authtypecode,value_builder,kohafield,seealso,hidden,isurl,link,defaultvalue,maxlength
901 FROM marc_subfield_structure
902 WHERE frameworkcode=?
903 ORDER BY tagfield,tagsubfield
907 $sth->execute($frameworkcode);
910 my $authorised_value;
922 ( $tag, $subfield, $liblibrarian, $libopac, $tab, $mandatory, $repeatable, $authorised_value,
923 $authtypecode, $value_builder, $kohafield, $seealso, $hidden, $isurl, $link, $defaultvalue,
928 $res->{$tag}->{$subfield}->{lib} = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
929 $res->{$tag}->{$subfield}->{tab} = $tab;
930 $res->{$tag}->{$subfield}->{mandatory} = $mandatory;
931 $res->{$tag}->{$subfield}->{repeatable} = $repeatable;
932 $res->{$tag}->{$subfield}->{authorised_value} = $authorised_value;
933 $res->{$tag}->{$subfield}->{authtypecode} = $authtypecode;
934 $res->{$tag}->{$subfield}->{value_builder} = $value_builder;
935 $res->{$tag}->{$subfield}->{kohafield} = $kohafield;
936 $res->{$tag}->{$subfield}->{seealso} = $seealso;
937 $res->{$tag}->{$subfield}->{hidden} = $hidden;
938 $res->{$tag}->{$subfield}->{isurl} = $isurl;
939 $res->{$tag}->{$subfield}->{'link'} = $link;
940 $res->{$tag}->{$subfield}->{defaultvalue} = $defaultvalue;
941 $res->{$tag}->{$subfield}->{maxlength} = $maxlength;
944 $cache->set_in_cache($cache_key, $res);
948 =head2 GetUsedMarcStructure
950 The same function as GetMarcStructure except it just takes field
951 in tab 0-9. (used field)
953 my $results = GetUsedMarcStructure($frameworkcode);
955 C<$results> is a ref to an array which each case contains a ref
956 to a hash which each keys is the columns from marc_subfield_structure
958 C<$frameworkcode> is the framework code.
962 sub GetUsedMarcStructure {
963 my $frameworkcode = shift || '';
966 FROM marc_subfield_structure
968 AND frameworkcode = ?
969 ORDER BY tagfield, tagsubfield
971 my $sth = C4::Context->dbh->prepare($query);
972 $sth->execute($frameworkcode);
973 return $sth->fetchall_arrayref( {} );
978 =head2 GetMarcSubfieldStructure
980 my $structure = GetMarcSubfieldStructure($frameworkcode, [$params]);
982 Returns a reference to hash representing MARC subfield structure
983 for framework with framework code C<$frameworkcode>, C<$params> is
984 optional and may contain additional options.
988 =item C<$frameworkcode>
994 An optional hash reference with additional options.
995 The following options are supported:
1001 Pass { unsafe => 1 } do disable cached object cloning,
1002 and instead get a shared reference, resulting in better
1003 performance (but care must be taken so that retured object
1006 Note: If you call GetMarcSubfieldStructure with unsafe => 1, do not modify or
1007 even autovivify its contents. It is a cached/shared data structure. Your
1008 changes would be passed around in subsequent calls.
1016 sub GetMarcSubfieldStructure {
1017 my ( $frameworkcode, $params ) = @_;
1019 $frameworkcode //= '';
1021 my $cache = Koha::Caches->get_instance();
1022 my $cache_key = "MarcSubfieldStructure-$frameworkcode";
1023 my $cached = $cache->get_from_cache($cache_key, { unsafe => ($params && $params->{unsafe}) });
1024 return $cached if $cached;
1026 my $dbh = C4::Context->dbh;
1027 # We moved to selectall_arrayref since selectall_hashref does not
1028 # keep duplicate mappings on kohafield (like place in 260 vs 264)
1029 my $subfield_aref = $dbh->selectall_arrayref( q|
1031 FROM marc_subfield_structure
1032 WHERE frameworkcode = ?
1034 ORDER BY frameworkcode,tagfield,tagsubfield
1035 |, { Slice => {} }, $frameworkcode );
1036 # Now map the output to a hash structure
1037 my $subfield_structure = {};
1038 foreach my $row ( @$subfield_aref ) {
1039 push @{ $subfield_structure->{ $row->{kohafield} }}, $row;
1041 $cache->set_in_cache( $cache_key, $subfield_structure );
1042 return $subfield_structure;
1045 =head2 GetMarcFromKohaField
1047 ( $field,$subfield ) = GetMarcFromKohaField( $kohafield );
1048 @fields = GetMarcFromKohaField( $kohafield );
1049 $field = GetMarcFromKohaField( $kohafield );
1051 Returns the MARC fields & subfields mapped to $kohafield.
1052 Since the Default framework is considered as authoritative for such
1053 mappings, the former frameworkcode parameter is obsoleted.
1055 In list context all mappings are returned; there can be multiple
1056 mappings. Note that in the above example you could miss a second
1057 mappings in the first call.
1058 In scalar context only the field tag of the first mapping is returned.
1062 sub GetMarcFromKohaField {
1063 my ( $kohafield ) = @_;
1064 return unless $kohafield;
1065 # The next call uses the Default framework since it is AUTHORITATIVE
1066 # for all Koha to MARC mappings.
1067 my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
1069 foreach( @{ $mss->{$kohafield} } ) {
1070 push @retval, $_->{tagfield}, $_->{tagsubfield};
1072 return wantarray ? @retval : ( @retval ? $retval[0] : undef );
1075 =head2 GetMarcSubfieldStructureFromKohaField
1077 my $str = GetMarcSubfieldStructureFromKohaField( $kohafield );
1079 Returns marc subfield structure information for $kohafield.
1080 The Default framework is used, since it is authoritative for kohafield
1082 In list context returns a list of all hashrefs, since there may be
1083 multiple mappings. In scalar context the first hashref is returned.
1087 sub GetMarcSubfieldStructureFromKohaField {
1088 my ( $kohafield ) = @_;
1090 return unless $kohafield;
1092 # The next call uses the Default framework since it is AUTHORITATIVE
1093 # for all Koha to MARC mappings.
1094 my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
1095 return unless $mss->{$kohafield};
1096 return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
1099 =head2 GetMarcBiblio
1101 my $record = GetMarcBiblio({
1102 biblionumber => $biblionumber,
1103 embed_items => $embeditems,
1106 Returns MARC::Record representing a biblio record, or C<undef> if the
1107 biblionumber doesn't exist.
1109 Both embed_items and opac are optional.
1110 If embed_items is passed and is 1, items are embedded.
1111 If opac is passed and is 1, the record is filtered as needed.
1115 =item C<$biblionumber>
1119 =item C<$embeditems>
1121 set to true to include item information.
1125 set to true to make the result suited for OPAC view. This causes things like
1126 OpacHiddenItems to be applied.
1135 if (not defined $params) {
1136 carp 'GetMarcBiblio called without parameters';
1140 my $biblionumber = $params->{biblionumber};
1141 my $embeditems = $params->{embed_items} || 0;
1142 my $opac = $params->{opac} || 0;
1144 if (not defined $biblionumber) {
1145 carp 'GetMarcBiblio called with undefined biblionumber';
1149 my $dbh = C4::Context->dbh;
1150 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=? ");
1151 $sth->execute($biblionumber);
1152 my $row = $sth->fetchrow_hashref;
1153 my $biblioitemnumber = $row->{'biblioitemnumber'};
1154 my $marcxml = GetXmlBiblio( $biblionumber );
1155 $marcxml = StripNonXmlChars( $marcxml );
1156 my $frameworkcode = GetFrameworkCode($biblionumber);
1157 MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
1158 my $record = MARC::Record->new();
1162 MARC::Record::new_from_xml( $marcxml, "utf8",
1163 C4::Context->preference('marcflavour') );
1165 if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
1166 return unless $record;
1168 C4::Biblio::_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber,
1169 $biblioitemnumber );
1170 C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, $opac )
1182 my $marcxml = GetXmlBiblio($biblionumber);
1184 Returns biblio_metadata.metadata/marcxml of the biblionumber passed in parameter.
1185 The XML should only contain biblio information (item information is no longer stored in marcxml field)
1190 my ($biblionumber) = @_;
1191 my $dbh = C4::Context->dbh;
1192 return unless $biblionumber;
1193 my ($marcxml) = $dbh->selectrow_array(
1196 FROM biblio_metadata
1197 WHERE biblionumber=?
1198 AND format='marcxml'
1200 |, undef, $biblionumber, C4::Context->preference('marcflavour')
1205 =head2 GetCOinSBiblio
1207 my $coins = GetCOinSBiblio($record);
1209 Returns the COinS (a span) which can be included in a biblio record
1213 sub GetCOinSBiblio {
1216 # get the coin format
1218 carp 'GetCOinSBiblio called with undefined record';
1221 my $pos7 = substr $record->leader(), 7, 1;
1222 my $pos6 = substr $record->leader(), 6, 1;
1225 my ( $aulast, $aufirst ) = ( '', '' );
1234 my $titletype = 'b';
1236 # For the purposes of generating COinS metadata, LDR/06-07 can be
1237 # considered the same for UNIMARC and MARC21
1242 'b' => 'manuscript',
1244 'd' => 'manuscript',
1248 'i' => 'audioRecording',
1249 'j' => 'audioRecording',
1252 'm' => 'computerProgram',
1257 'a' => 'journalArticle',
1261 $genre = $fmts6->{$pos6} ? $fmts6->{$pos6} : 'book';
1263 if ( $genre eq 'book' ) {
1264 $genre = $fmts7->{$pos7} if $fmts7->{$pos7};
1267 ##### We must transform mtx to a valable mtx and document type ####
1268 if ( $genre eq 'book' ) {
1270 } elsif ( $genre eq 'journal' ) {
1273 } elsif ( $genre eq 'journalArticle' ) {
1281 $genre = ( $mtx eq 'dc' ) ? "&rft.type=$genre" : "&rft.genre=$genre";
1283 if ( C4::Context->preference("marcflavour") eq "UNIMARC" ) {
1286 $aulast = $record->subfield( '700', 'a' ) || '';
1287 $aufirst = $record->subfield( '700', 'b' ) || '';
1288 $oauthors = "&rft.au=$aufirst $aulast";
1291 if ( $record->field('200') ) {
1292 for my $au ( $record->field('200')->subfield('g') ) {
1293 $oauthors .= "&rft.au=$au";
1298 ? "&rft.title=" . $record->subfield( '200', 'a' )
1299 : "&rft.title=" . $record->subfield( '200', 'a' ) . "&rft.btitle=" . $record->subfield( '200', 'a' );
1300 $pubyear = $record->subfield( '210', 'd' ) || '';
1301 $publisher = $record->subfield( '210', 'c' ) || '';
1302 $isbn = $record->subfield( '010', 'a' ) || '';
1303 $issn = $record->subfield( '011', 'a' ) || '';
1306 # MARC21 need some improve
1309 if ( $record->field('100') ) {
1310 $oauthors .= "&rft.au=" . $record->subfield( '100', 'a' );
1314 if ( $record->field('700') ) {
1315 for my $au ( $record->field('700')->subfield('a') ) {
1316 $oauthors .= "&rft.au=$au";
1319 $title = "&rft." . $titletype . "title=" . $record->subfield( '245', 'a' );
1320 $subtitle = $record->subfield( '245', 'b' ) || '';
1321 $title .= $subtitle;
1322 if ($titletype eq 'a') {
1323 $pubyear = $record->field('008') || '';
1324 $pubyear = substr($pubyear->data(), 7, 4) if $pubyear;
1325 $isbn = $record->subfield( '773', 'z' ) || '';
1326 $issn = $record->subfield( '773', 'x' ) || '';
1327 if ($mtx eq 'journal') {
1328 $title .= "&rft.title=" . ( $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{} );
1330 $title .= "&rft.btitle=" . ( $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{} );
1332 foreach my $rel ($record->subfield( '773', 'g' )) {
1339 $pubyear = $record->subfield( '260', 'c' ) || '';
1340 $publisher = $record->subfield( '260', 'b' ) || '';
1341 $isbn = $record->subfield( '020', 'a' ) || '';
1342 $issn = $record->subfield( '022', 'a' ) || '';
1347 "ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3A$mtx$genre$title&rft.isbn=$isbn&rft.issn=$issn&rft.aulast=$aulast&rft.aufirst=$aufirst$oauthors&rft.pub=$publisher&rft.date=$pubyear&rft.pages=$pages";
1348 $coins_value =~ s/(\ |&[^a])/\+/g;
1349 $coins_value =~ s/\"/\"\;/g;
1351 #<!-- TMPL_VAR NAME="ocoins_format" -->&rft.au=<!-- TMPL_VAR NAME="author" -->&rft.btitle=<!-- TMPL_VAR NAME="title" -->&rft.date=<!-- TMPL_VAR NAME="publicationyear" -->&rft.pages=<!-- TMPL_VAR NAME="pages" -->&rft.isbn=<!-- TMPL_VAR NAME=amazonisbn -->&rft.aucorp=&rft.place=<!-- TMPL_VAR NAME="place" -->&rft.pub=<!-- TMPL_VAR NAME="publishercode" -->&rft.edition=<!-- TMPL_VAR NAME="edition" -->&rft.series=<!-- TMPL_VAR NAME="series" -->&rft.genre="
1353 return $coins_value;
1359 return the prices in accordance with the Marc format.
1361 returns 0 if no price found
1362 returns undef if called without a marc record or with
1363 an unrecognized marc format
1368 my ( $record, $marcflavour ) = @_;
1370 carp 'GetMarcPrice called on undefined record';
1377 if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
1378 @listtags = ('345', '020');
1380 } elsif ( $marcflavour eq "UNIMARC" ) {
1381 @listtags = ('345', '010');
1387 for my $field ( $record->field(@listtags) ) {
1388 for my $subfield_value ($field->subfield($subfield)){
1390 $subfield_value = MungeMarcPrice( $subfield_value );
1391 return $subfield_value if ($subfield_value);
1394 return 0; # no price found
1397 =head2 MungeMarcPrice
1399 Return the best guess at what the actual price is from a price field.
1403 sub MungeMarcPrice {
1405 return unless ( $price =~ m/\d/ ); ## No digits means no price.
1406 # Look for the currency symbol and the normalized code of the active currency, if it's there,
1407 my $active_currency = Koha::Acquisition::Currencies->get_active;
1408 my $symbol = $active_currency->symbol;
1409 my $isocode = $active_currency->isocode;
1410 $isocode = $active_currency->currency unless defined $isocode;
1413 my @matches =($price=~ /
1415 ( # start of capturing parenthesis
1417 (?:[\p{Sc}\p{L}\/.]){1,4} # any character from Currency signs or Letter Unicode categories or slash or dot within 1 to 4 occurrences : call this whole block 'symbol block'
1418 |(?:\d+[\p{P}\s]?){1,4} # or else at least one digit followed or not by a punctuation sign or whitespace, all these within 1 to 4 occurrences : call this whole block 'digits block'
1420 \s?\p{Sc}?\s? # followed or not by a whitespace. \p{Sc}?\s? are for cases like '25$ USD'
1422 (?:[\p{Sc}\p{L}\/.]){1,4} # followed by same block as symbol block
1423 |(?:\d+[\p{P}\s]?){1,4} # or by same block as digits block
1425 \s?\p{L}{0,4}\s? # followed or not by a whitespace. \p{L}{0,4}\s? are for cases like '$9.50 USD'
1426 ) # end of capturing parenthesis
1427 (?:\p{P}|\z) # followed by a punctuation sign or by the end of the string
1431 foreach ( @matches ) {
1432 $localprice = $_ and last if index($_, $isocode)>=0;
1434 if ( !$localprice ) {
1435 foreach ( @matches ) {
1436 $localprice = $_ and last if $_=~ /(^|[^\p{Sc}\p{L}\/])\Q$symbol\E([^\p{Sc}\p{L}\/]+\z|\z)/;
1441 if ( $localprice ) {
1442 $price = $localprice;
1444 ## Grab the first number in the string ( can use commas or periods for thousands separator and/or decimal separator )
1445 ( $price ) = $price =~ m/([\d\,\.]+[[\,\.]\d\d]?)/;
1447 # eliminate symbol/isocode, space and any final dot from the string
1448 $price =~ s/[\p{Sc}\p{L}\/ ]|\.$//g;
1449 # remove comma,dot when used as separators from hundreds
1450 $price =~s/[\,\.](\d{3})/$1/g;
1451 # convert comma to dot to ensure correct display of decimals if existing
1457 =head2 GetMarcQuantity
1459 return the quantity of a book. Used in acquisition only, when importing a file an iso2709 from a bookseller
1460 Warning : this is not really in the marc standard. In Unimarc, Electre (the most widely used bookseller) use the 969$a
1462 returns 0 if no quantity found
1463 returns undef if called without a marc record or with
1464 an unrecognized marc format
1468 sub GetMarcQuantity {
1469 my ( $record, $marcflavour ) = @_;
1471 carp 'GetMarcQuantity called on undefined record';
1478 if ( $marcflavour eq "MARC21" ) {
1480 } elsif ( $marcflavour eq "UNIMARC" ) {
1481 @listtags = ('969');
1487 for my $field ( $record->field(@listtags) ) {
1488 for my $subfield_value ($field->subfield($subfield)){
1490 if ($subfield_value) {
1491 # in France, the cents separator is the , but sometimes, ppl use a .
1492 # in this case, the price will be x100 when unformatted ! Replace the . by a , to get a proper price calculation
1493 $subfield_value =~ s/\./,/ if C4::Context->preference("CurrencyFormat") eq "FR";
1494 return $subfield_value;
1498 return 0; # no price found
1502 =head2 GetAuthorisedValueDesc
1504 my $subfieldvalue =get_authorised_value_desc(
1505 $tag, $subf[$i][0],$subf[$i][1], '', $taglib, $category, $opac);
1507 Retrieve the complete description for a given authorised value.
1509 Now takes $category and $value pair too.
1511 my $auth_value_desc =GetAuthorisedValueDesc(
1512 '','', 'DVD' ,'','','CCODE');
1514 If the optional $opac parameter is set to a true value, displays OPAC
1515 descriptions rather than normal ones when they exist.
1519 sub GetAuthorisedValueDesc {
1520 my ( $tag, $subfield, $value, $framework, $tagslib, $category, $opac ) = @_;
1524 return $value unless defined $tagslib->{$tag}->{$subfield}->{'authorised_value'};
1527 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1528 return Koha::Libraries->find($value)->branchname;
1532 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
1533 my $itemtype = Koha::ItemTypes->find( $value );
1534 return $itemtype ? $itemtype->translated_description : q||;
1537 #---- "true" authorized value
1538 $category = $tagslib->{$tag}->{$subfield}->{'authorised_value'};
1541 my $dbh = C4::Context->dbh;
1542 if ( $category ne "" ) {
1543 my $sth = $dbh->prepare( "SELECT lib, lib_opac FROM authorised_values WHERE category = ? AND authorised_value = ?" );
1544 $sth->execute( $category, $value );
1545 my $data = $sth->fetchrow_hashref;
1546 return ( $opac && $data->{'lib_opac'} ) ? $data->{'lib_opac'} : $data->{'lib'};
1548 return $value; # if nothing is found return the original value
1552 =head2 GetMarcControlnumber
1554 $marccontrolnumber = GetMarcControlnumber($record,$marcflavour);
1556 Get the control number / record Identifier from the MARC record and return it.
1560 sub GetMarcControlnumber {
1561 my ( $record, $marcflavour ) = @_;
1563 carp 'GetMarcControlnumber called on undefined record';
1566 my $controlnumber = "";
1567 # Control number or Record identifier are the same field in MARC21, UNIMARC and NORMARC
1568 # Keep $marcflavour for possible later use
1569 if ($marcflavour eq "MARC21" || $marcflavour eq "UNIMARC" || $marcflavour eq "NORMARC") {
1570 my $controlnumberField = $record->field('001');
1571 if ($controlnumberField) {
1572 $controlnumber = $controlnumberField->data();
1575 return $controlnumber;
1580 $marcisbnsarray = GetMarcISBN( $record, $marcflavour );
1582 Get all ISBNs from the MARC record and returns them in an array.
1583 ISBNs stored in different fields depending on MARC flavour
1588 my ( $record, $marcflavour ) = @_;
1590 carp 'GetMarcISBN called on undefined record';
1594 if ( $marcflavour eq "UNIMARC" ) {
1596 } else { # assume marc21 if not unimarc
1601 foreach my $field ( $record->field($scope) ) {
1602 my $isbn = $field->subfield( 'a' );
1603 if ( $isbn ne "" ) {
1604 push @marcisbns, $isbn;
1614 $marcissnsarray = GetMarcISSN( $record, $marcflavour );
1616 Get all valid ISSNs from the MARC record and returns them in an array.
1617 ISSNs are stored in different fields depending on MARC flavour
1622 my ( $record, $marcflavour ) = @_;
1624 carp 'GetMarcISSN called on undefined record';
1628 if ( $marcflavour eq "UNIMARC" ) {
1631 else { # assume MARC21 or NORMARC
1635 foreach my $field ( $record->field($scope) ) {
1636 push @marcissns, $field->subfield( 'a' )
1637 if ( $field->subfield( 'a' ) ne "" );
1644 $marcnotesarray = GetMarcNotes( $record, $marcflavour );
1646 Get all notes from the MARC record and returns them in an array.
1647 The notes are stored in different fields depending on MARC flavour.
1648 MARC21 5XX $u subfields receive special attention as they are URIs.
1653 my ( $record, $marcflavour ) = @_;
1655 carp 'GetMarcNotes called on undefined record';
1659 my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
1661 my %blacklist = map { $_ => 1 }
1662 split( /,/, C4::Context->preference('NotesBlacklist'));
1663 foreach my $field ( $record->field($scope) ) {
1664 my $tag = $field->tag();
1665 next if $blacklist{ $tag };
1666 if( $marcflavour ne 'UNIMARC' && $field->subfield('u') ) {
1667 # Field 5XX$u always contains URI
1668 # Examples: 505u, 506u, 510u, 514u, 520u, 530u, 538u, 540u, 542u, 552u, 555u, 561u, 563u, 583u
1669 # We first push the other subfields, then all $u's separately
1670 # Leave further actions to the template (see e.g. opac-detail)
1672 join '', ( 'a' .. 't', 'v' .. 'z', '0' .. '9' ); # excl 'u'
1673 push @marcnotes, { marcnote => $field->as_string($othersub) };
1674 foreach my $sub ( $field->subfield('u') ) {
1675 $sub =~ s/^\s+|\s+$//g; # trim
1676 push @marcnotes, { marcnote => $sub };
1679 push @marcnotes, { marcnote => $field->as_string() };
1685 =head2 GetMarcSubjects
1687 $marcsubjcts = GetMarcSubjects($record,$marcflavour);
1689 Get all subjects from the MARC record and returns them in an array.
1690 The subjects are stored in different fields depending on MARC flavour
1694 sub GetMarcSubjects {
1695 my ( $record, $marcflavour ) = @_;
1697 carp 'GetMarcSubjects called on undefined record';
1700 my ( $mintag, $maxtag, $fields_filter );
1701 if ( $marcflavour eq "UNIMARC" ) {
1704 $fields_filter = '6..';
1705 } else { # marc21/normarc
1708 $fields_filter = '6..';
1713 my $subject_limit = C4::Context->preference("TraceCompleteSubfields") ? 'su,complete-subfield' : 'su';
1714 my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
1716 foreach my $field ( $record->field($fields_filter) ) {
1717 next unless ($field->tag() >= $mintag && $field->tag() <= $maxtag);
1719 my @subfields = $field->subfields();
1722 # if there is an authority link, build the links with an= subfield9
1723 my $subfield9 = $field->subfield('9');
1726 my $linkvalue = $subfield9;
1727 $linkvalue =~ s/(\(|\))//g;
1728 @link_loop = ( { limit => 'an', 'link' => $linkvalue } );
1729 $authoritylink = $linkvalue
1733 for my $subject_subfield (@subfields) {
1734 next if ( $subject_subfield->[0] eq '9' );
1736 # don't load unimarc subfields 3,4,5
1737 next if ( ( $marcflavour eq "UNIMARC" ) and ( $subject_subfield->[0] =~ /2|3|4|5/ ) );
1738 # don't load MARC21 subfields 2 (FIXME: any more subfields??)
1739 next if ( ( $marcflavour eq "MARC21" ) and ( $subject_subfield->[0] =~ /2/ ) );
1741 my $code = $subject_subfield->[0];
1742 my $value = $subject_subfield->[1];
1743 my $linkvalue = $value;
1744 $linkvalue =~ s/(\(|\))//g;
1745 # if no authority link, build a search query
1746 unless ($subfield9) {
1748 limit => $subject_limit,
1749 'link' => $linkvalue,
1750 operator => (scalar @link_loop) ? ' and ' : undef
1753 my @this_link_loop = @link_loop;
1755 unless ( $code eq '0' ) {
1756 push @subfields_loop, {
1759 link_loop => \@this_link_loop,
1760 separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
1765 push @marcsubjects, {
1766 MARCSUBJECT_SUBFIELDS_LOOP => \@subfields_loop,
1767 authoritylink => $authoritylink,
1768 } if $authoritylink || @subfields_loop;
1771 return \@marcsubjects;
1772 } #end getMARCsubjects
1774 =head2 GetMarcAuthors
1776 authors = GetMarcAuthors($record,$marcflavour);
1778 Get all authors from the MARC record and returns them in an array.
1779 The authors are stored in different fields depending on MARC flavour
1783 sub GetMarcAuthors {
1784 my ( $record, $marcflavour ) = @_;
1786 carp 'GetMarcAuthors called on undefined record';
1789 my ( $mintag, $maxtag, $fields_filter );
1791 # tagslib useful only for UNIMARC author responsibilities
1793 if ( $marcflavour eq "UNIMARC" ) {
1794 # FIXME : we don't have the framework available, we take the default framework. May be buggy on some setups, will be usually correct.
1795 $tagslib = GetMarcStructure( 1, '', { unsafe => 1 });
1798 $fields_filter = '7..';
1799 } else { # marc21/normarc
1802 $fields_filter = '7..';
1806 my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
1808 foreach my $field ( $record->field($fields_filter) ) {
1809 next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
1812 my @subfields = $field->subfields();
1815 # if there is an authority link, build the link with Koha-Auth-Number: subfield9
1816 my $subfield9 = $field->subfield('9');
1818 my $linkvalue = $subfield9;
1819 $linkvalue =~ s/(\(|\))//g;
1820 @link_loop = ( { 'limit' => 'an', 'link' => $linkvalue } );
1825 for my $authors_subfield (@subfields) {
1826 next if ( $authors_subfield->[0] eq '9' );
1828 # unimarc3 contains the $3 of the author for UNIMARC.
1829 # For french academic libraries, it's the "ppn", and it's required for idref webservice
1830 $unimarc3 = $authors_subfield->[1] if $marcflavour eq 'UNIMARC' and $authors_subfield->[0] =~ /3/;
1832 # don't load unimarc subfields 3, 5
1833 next if ( $marcflavour eq 'UNIMARC' and ( $authors_subfield->[0] =~ /3|5/ ) );
1835 my $code = $authors_subfield->[0];
1836 my $value = $authors_subfield->[1];
1837 my $linkvalue = $value;
1838 $linkvalue =~ s/(\(|\))//g;
1839 # UNIMARC author responsibility
1840 if ( $marcflavour eq 'UNIMARC' and $code eq '4' ) {
1841 $value = GetAuthorisedValueDesc( $field->tag(), $code, $value, '', $tagslib );
1842 $linkvalue = "($value)";
1844 # if no authority link, build a search query
1845 unless ($subfield9) {
1848 'link' => $linkvalue,
1849 operator => (scalar @link_loop) ? ' and ' : undef
1852 my @this_link_loop = @link_loop;
1854 unless ( $code eq '0') {
1855 push @subfields_loop, {
1856 tag => $field->tag(),
1859 link_loop => \@this_link_loop,
1860 separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
1864 push @marcauthors, {
1865 MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop,
1866 authoritylink => $subfield9,
1867 unimarc3 => $unimarc3
1870 return \@marcauthors;
1875 $marcurls = GetMarcUrls($record,$marcflavour);
1877 Returns arrayref of URLs from MARC data, suitable to pass to tmpl loop.
1878 Assumes web resources (not uncommon in MARC21 to omit resource type ind)
1883 my ( $record, $marcflavour ) = @_;
1885 carp 'GetMarcUrls called on undefined record';
1890 for my $field ( $record->field('856') ) {
1892 for my $note ( $field->subfield('z') ) {
1893 push @notes, { note => $note };
1895 my @urls = $field->subfield('u');
1896 foreach my $url (@urls) {
1897 $url =~ s/^\s+|\s+$//g; # trim
1899 if ( $marcflavour eq 'MARC21' ) {
1900 my $s3 = $field->subfield('3');
1901 my $link = $field->subfield('y');
1902 unless ( $url =~ /^\w+:/ ) {
1903 if ( $field->indicator(1) eq '7' ) {
1904 $url = $field->subfield('2') . "://" . $url;
1905 } elsif ( $field->indicator(1) eq '1' ) {
1906 $url = 'ftp://' . $url;
1909 # properly, this should be if ind1=4,
1910 # however we will assume http protocol since we're building a link.
1911 $url = 'http://' . $url;
1915 # TODO handle ind 2 (relationship)
1920 $marcurl->{'linktext'} = $link || $s3 || C4::Context->preference('URLLinkText') || $url;
1921 $marcurl->{'part'} = $s3 if ($link);
1922 $marcurl->{'toc'} = 1 if ( defined($s3) && $s3 =~ /^[Tt]able/ );
1924 $marcurl->{'linktext'} = $field->subfield('2') || C4::Context->preference('URLLinkText') || $url;
1925 $marcurl->{'MARCURL'} = $url;
1927 push @marcurls, $marcurl;
1933 =head2 GetMarcSeries
1935 $marcseriesarray = GetMarcSeries($record,$marcflavour);
1937 Get all series from the MARC record and returns them in an array.
1938 The series are stored in different fields depending on MARC flavour
1943 my ( $record, $marcflavour ) = @_;
1945 carp 'GetMarcSeries called on undefined record';
1949 my ( $mintag, $maxtag, $fields_filter );
1950 if ( $marcflavour eq "UNIMARC" ) {
1953 $fields_filter = '2..';
1954 } else { # marc21/normarc
1957 $fields_filter = '4..';
1961 my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
1963 foreach my $field ( $record->field($fields_filter) ) {
1964 next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
1966 my @subfields = $field->subfields();
1969 for my $series_subfield (@subfields) {
1971 # ignore $9, used for authority link
1972 next if ( $series_subfield->[0] eq '9' );
1975 my $code = $series_subfield->[0];
1976 my $value = $series_subfield->[1];
1977 my $linkvalue = $value;
1978 $linkvalue =~ s/(\(|\))//g;
1980 # see if this is an instance of a volume
1981 if ( $code eq 'v' ) {
1986 'link' => $linkvalue,
1987 operator => (scalar @link_loop) ? ' and ' : undef
1990 if ($volume_number) {
1991 push @subfields_loop, { volumenum => $value };
1993 push @subfields_loop, {
1996 link_loop => \@link_loop,
1997 separator => (scalar @subfields_loop) ? $AuthoritySeparator : '',
1998 volumenum => $volume_number,
2002 push @marcseries, { MARCSERIES_SUBFIELDS_LOOP => \@subfields_loop };
2005 return \@marcseries;
2006 } #end getMARCseriess
2010 $marchostsarray = GetMarcHosts($record,$marcflavour);
2012 Get all host records (773s MARC21, 461 UNIMARC) from the MARC record and returns them in an array.
2017 my ( $record, $marcflavour ) = @_;
2019 carp 'GetMarcHosts called on undefined record';
2023 my ( $tag,$title_subf,$bibnumber_subf,$itemnumber_subf);
2024 $marcflavour ||="MARC21";
2025 if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
2028 $bibnumber_subf ="0";
2029 $itemnumber_subf='9';
2031 elsif ($marcflavour eq "UNIMARC") {
2034 $bibnumber_subf ="0";
2035 $itemnumber_subf='9';
2040 foreach my $field ( $record->field($tag)) {
2044 my $hostbiblionumber = $field->subfield("$bibnumber_subf");
2045 my $hosttitle = $field->subfield($title_subf);
2046 my $hostitemnumber=$field->subfield($itemnumber_subf);
2047 push @fields_loop, { hostbiblionumber => $hostbiblionumber, hosttitle => $hosttitle, hostitemnumber => $hostitemnumber};
2048 push @marchosts, { MARCHOSTS_FIELDS_LOOP => \@fields_loop };
2051 my $marchostsarray = \@marchosts;
2052 return $marchostsarray;
2055 =head2 UpsertMarcSubfield
2057 my $record = C4::Biblio::UpsertMarcSubfield($MARC::Record, $fieldTag, $subfieldCode, $subfieldContent);
2061 sub UpsertMarcSubfield {
2062 my ($record, $tag, $code, $content) = @_;
2063 my $f = $record->field($tag);
2066 $f->update( $code => $content );
2069 my $f = MARC::Field->new( $tag, '', '', $code => $content);
2070 $record->insert_fields_ordered( $f );
2074 =head2 UpsertMarcControlField
2076 my $record = C4::Biblio::UpsertMarcControlField($MARC::Record, $fieldTag, $content);
2080 sub UpsertMarcControlField {
2081 my ($record, $tag, $content) = @_;
2082 die "UpsertMarcControlField() \$tag '$tag' is not a control field\n" unless 0+$tag < 10;
2083 my $f = $record->field($tag);
2086 $f->update( $content );
2089 my $f = MARC::Field->new($tag, $content);
2090 $record->insert_fields_ordered( $f );
2094 =head2 GetFrameworkCode
2096 $frameworkcode = GetFrameworkCode( $biblionumber )
2100 sub GetFrameworkCode {
2101 my ($biblionumber) = @_;
2102 my $dbh = C4::Context->dbh;
2103 my $sth = $dbh->prepare("SELECT frameworkcode FROM biblio WHERE biblionumber=?");
2104 $sth->execute($biblionumber);
2105 my ($frameworkcode) = $sth->fetchrow;
2106 return $frameworkcode;
2109 =head2 TransformKohaToMarc
2111 $record = TransformKohaToMarc( $hash [, $params ] )
2113 This function builds a (partial) MARC::Record from a hash.
2114 Hash entries can be from biblio, biblioitems or items.
2115 The params hash includes the parameter no_split used in C4::Items.
2117 This function is called in acquisition module, to create a basic catalogue
2118 entry from user entry.
2123 sub TransformKohaToMarc {
2124 my ( $hash, $params ) = @_;
2125 my $record = MARC::Record->new();
2126 SetMarcUnicodeFlag( $record, C4::Context->preference("marcflavour") );
2128 # In the next call we use the Default framework, since it is considered
2129 # authoritative for Koha to Marc mappings.
2130 my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # do not change framewok
2132 while ( my ($kohafield, $value) = each %$hash ) {
2133 foreach my $fld ( @{ $mss->{$kohafield} } ) {
2134 my $tagfield = $fld->{tagfield};
2135 my $tagsubfield = $fld->{tagsubfield};
2137 my @values = $params->{no_split}
2139 : split(/\s?\|\s?/, $value, -1);
2140 foreach my $value ( @values ) {
2141 next if $value eq '';
2142 $tag_hr->{$tagfield} //= [];
2143 push @{$tag_hr->{$tagfield}}, [($tagsubfield, $value)];
2147 foreach my $tag (sort keys %$tag_hr) {
2148 my @sfl = @{$tag_hr->{$tag}};
2149 @sfl = sort { $a->[0] cmp $b->[0]; } @sfl;
2150 @sfl = map { @{$_}; } @sfl;
2151 # Special care for control fields: remove the subfield indication @
2152 # and do not insert indicators.
2153 my @ind = $tag < 10 ? () : ( " ", " " );
2154 @sfl = grep { $_ ne '@' } @sfl if $tag < 10;
2155 $record->insert_fields_ordered( MARC::Field->new($tag, @ind, @sfl) );
2160 =head2 PrepHostMarcField
2162 $hostfield = PrepHostMarcField ( $hostbiblionumber,$hostitemnumber,$marcflavour )
2164 This function returns a host field populated with data from the host record, the field can then be added to an analytical record
2168 sub PrepHostMarcField {
2169 my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
2170 $marcflavour ||="MARC21";
2173 my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
2174 my $item = C4::Items::GetItem($hostitemnumber);
2177 if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
2181 if ($hostrecord->subfield('100','a')){
2182 $mainentry = $hostrecord->subfield('100','a');
2183 } elsif ($hostrecord->subfield('110','a')){
2184 $mainentry = $hostrecord->subfield('110','a');
2186 $mainentry = $hostrecord->subfield('111','a');
2189 # qualification info
2191 if (my $field260 = $hostrecord->field('260')){
2192 $qualinfo = $field260->as_string( 'abc' );
2197 my $ed = $hostrecord->subfield('250','a');
2198 my $barcode = $item->{'barcode'};
2199 my $title = $hostrecord->subfield('245','a');
2201 # record control number, 001 with 003 and prefix
2203 if ($hostrecord->field('001')){
2204 $recctrlno = $hostrecord->field('001')->data();
2205 if ($hostrecord->field('003')){
2206 $recctrlno = '('.$hostrecord->field('003')->data().')'.$recctrlno;
2211 my $issn = $hostrecord->subfield('022','a');
2212 my $isbn = $hostrecord->subfield('020','a');
2215 $hostmarcfield = MARC::Field->new(
2217 '0' => $hostbiblionumber,
2218 '9' => $hostitemnumber,
2228 } elsif ($marcflavour eq "UNIMARC") {
2229 $hostmarcfield = MARC::Field->new(
2231 '0' => $hostbiblionumber,
2232 't' => $hostrecord->subfield('200','a'),
2233 '9' => $hostitemnumber
2237 return $hostmarcfield;
2240 =head2 TransformHtmlToXml
2242 $xml = TransformHtmlToXml( $tags, $subfields, $values, $indicator,
2243 $ind_tag, $auth_type )
2245 $auth_type contains :
2249 =item - nothing : rebuild a biblio. In UNIMARC the encoding is in 100$a pos 26/27
2251 =item - UNIMARCAUTH : rebuild an authority. In UNIMARC, the encoding is in 100$a pos 13/14
2253 =item - ITEM : rebuild an item : in UNIMARC, 100$a, it's in the biblio ! (otherwise, we would get 2 100 fields !)
2259 sub TransformHtmlToXml {
2260 my ( $tags, $subfields, $values, $indicator, $ind_tag, $auth_type ) = @_;
2261 # NOTE: The parameter $ind_tag is NOT USED -- BZ 11247
2263 my $xml = MARC::File::XML::header('UTF-8');
2264 $xml .= "<record>\n";
2265 $auth_type = C4::Context->preference('marcflavour') unless $auth_type;
2266 MARC::File::XML->default_record_format($auth_type);
2268 # in UNIMARC, field 100 contains the encoding
2269 # check that there is one, otherwise the
2270 # MARC::Record->new_from_xml will fail (and Koha will die)
2271 my $unimarc_and_100_exist = 0;
2272 $unimarc_and_100_exist = 1 if $auth_type eq 'ITEM'; # if we rebuild an item, no need of a 100 field
2278 for ( my $i = 0 ; $i < @$tags ; $i++ ) {
2280 if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and @$tags[$i] eq "100" and @$subfields[$i] eq "a" ) {
2282 # if we have a 100 field and it's values are not correct, skip them.
2283 # if we don't have any valid 100 field, we will create a default one at the end
2284 my $enc = substr( @$values[$i], 26, 2 );
2285 if ( $enc eq '01' or $enc eq '50' or $enc eq '03' ) {
2286 $unimarc_and_100_exist = 1;
2291 @$values[$i] =~ s/&/&/g;
2292 @$values[$i] =~ s/</</g;
2293 @$values[$i] =~ s/>/>/g;
2294 @$values[$i] =~ s/"/"/g;
2295 @$values[$i] =~ s/'/'/g;
2297 if ( ( @$tags[$i] ne $prevtag ) ) {
2298 $close_last_tag = 0;
2299 $j++ unless ( @$tags[$i] eq "" );
2300 my $indicator1 = eval { substr( @$indicator[$j], 0, 1 ) };
2301 my $indicator2 = eval { substr( @$indicator[$j], 1, 1 ) };
2302 my $ind1 = _default_ind_to_space($indicator1);
2304 if ( @$indicator[$j] ) {
2305 $ind2 = _default_ind_to_space($indicator2);
2307 warn "Indicator in @$tags[$i] is empty";
2311 $xml .= "</datafield>\n";
2312 if ( ( @$tags[$i] && @$tags[$i] > 10 )
2313 && ( @$values[$i] ne "" ) ) {
2314 $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
2315 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
2317 $close_last_tag = 1;
2322 if ( @$values[$i] ne "" ) {
2325 if ( @$tags[$i] eq "000" ) {
2326 $xml .= "<leader>@$values[$i]</leader>\n";
2329 # rest of the fixed fields
2330 } elsif ( @$tags[$i] < 10 ) {
2331 $xml .= "<controlfield tag=\"@$tags[$i]\">@$values[$i]</controlfield>\n";
2334 $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
2335 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
2337 $close_last_tag = 1;
2341 } else { # @$tags[$i] eq $prevtag
2342 my $indicator1 = eval { substr( @$indicator[$j], 0, 1 ) };
2343 my $indicator2 = eval { substr( @$indicator[$j], 1, 1 ) };
2344 my $ind1 = _default_ind_to_space($indicator1);
2346 if ( @$indicator[$j] ) {
2347 $ind2 = _default_ind_to_space($indicator2);
2349 warn "Indicator in @$tags[$i] is empty";
2352 if ( @$values[$i] eq "" ) {
2355 $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
2357 $close_last_tag = 1;
2359 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
2362 $prevtag = @$tags[$i];
2364 $xml .= "</datafield>\n" if $close_last_tag;
2365 if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and !$unimarc_and_100_exist ) {
2367 # warn "SETTING 100 for $auth_type";
2368 my $string = strftime( "%Y%m%d", localtime(time) );
2370 # set 50 to position 26 is biblios, 13 if authorities
2372 $pos = 13 if $auth_type eq 'UNIMARCAUTH';
2373 $string = sprintf( "%-*s", 35, $string );
2374 substr( $string, $pos, 6, "50" );
2375 $xml .= "<datafield tag=\"100\" ind1=\"\" ind2=\"\">\n";
2376 $xml .= "<subfield code=\"a\">$string</subfield>\n";
2377 $xml .= "</datafield>\n";
2379 $xml .= "</record>\n";
2380 $xml .= MARC::File::XML::footer();
2384 =head2 _default_ind_to_space
2386 Passed what should be an indicator returns a space
2387 if its undefined or zero length
2391 sub _default_ind_to_space {
2393 if ( !defined $s || $s eq q{} ) {
2399 =head2 TransformHtmlToMarc
2401 L<$record> = TransformHtmlToMarc(L<$cgi>)
2402 L<$cgi> is the CGI object which contains the values for subfields
2404 'tag_010_indicator1_531951' ,
2405 'tag_010_indicator2_531951' ,
2406 'tag_010_code_a_531951_145735' ,
2407 'tag_010_subfield_a_531951_145735' ,
2408 'tag_200_indicator1_873510' ,
2409 'tag_200_indicator2_873510' ,
2410 'tag_200_code_a_873510_673465' ,
2411 'tag_200_subfield_a_873510_673465' ,
2412 'tag_200_code_b_873510_704318' ,
2413 'tag_200_subfield_b_873510_704318' ,
2414 'tag_200_code_e_873510_280822' ,
2415 'tag_200_subfield_e_873510_280822' ,
2416 'tag_200_code_f_873510_110730' ,
2417 'tag_200_subfield_f_873510_110730' ,
2419 L<$record> is the MARC::Record object.
2423 sub TransformHtmlToMarc {
2424 my ($cgi, $isbiblio) = @_;
2426 my @params = $cgi->multi_param();
2428 # explicitly turn on the UTF-8 flag for all
2429 # 'tag_' parameters to avoid incorrect character
2430 # conversion later on
2431 my $cgi_params = $cgi->Vars;
2432 foreach my $param_name ( keys %$cgi_params ) {
2433 if ( $param_name =~ /^tag_/ ) {
2434 my $param_value = $cgi_params->{$param_name};
2435 unless ( Encode::is_utf8( $param_value ) ) {
2436 $cgi_params->{$param_name} = Encode::decode('UTF-8', $param_value );
2441 # creating a new record
2442 my $record = MARC::Record->new();
2444 my ($biblionumbertagfield, $biblionumbertagsubfield) = (-1, -1);
2445 ($biblionumbertagfield, $biblionumbertagsubfield) =
2446 &GetMarcFromKohaField( "biblio.biblionumber", '' ) if $isbiblio;
2447 #FIXME This code assumes that the CGI params will be in the same order as the fields in the template; this is no absolute guarantee!
2448 for (my $i = 0; $params[$i]; $i++ ) { # browse all CGI params
2449 my $param = $params[$i];
2452 # if we are on biblionumber, store it in the MARC::Record (it may not be in the edited fields)
2453 if ( $param eq 'biblionumber' ) {
2454 if ( $biblionumbertagfield < 10 ) {
2455 $newfield = MARC::Field->new( $biblionumbertagfield, scalar $cgi->param($param), );
2457 $newfield = MARC::Field->new( $biblionumbertagfield, '', '', "$biblionumbertagsubfield" => scalar $cgi->param($param), );
2459 push @fields, $newfield if ($newfield);
2460 } elsif ( $param =~ /^tag_(\d*)_indicator1_/ ) { # new field start when having 'input name="..._indicator1_..."
2463 my $ind1 = _default_ind_to_space( substr( $cgi->param($param), 0, 1 ) );
2464 my $ind2 = _default_ind_to_space( substr( $cgi->param( $params[ $i + 1 ] ), 0, 1 ) );
2468 if ( $tag < 10 ) { # no code for theses fields
2469 # in MARC editor, 000 contains the leader.
2470 next if $tag == $biblionumbertagfield;
2471 my $fval= $cgi->param($params[$j+1]);
2472 if ( $tag eq '000' ) {
2473 # Force a fake leader even if not provided to avoid crashing
2474 # during decoding MARC record containing UTF-8 characters
2476 length( $fval ) == 24
2481 # between 001 and 009 (included)
2482 } elsif ( $fval ne '' ) {
2483 $newfield = MARC::Field->new( $tag, $fval, );
2486 # > 009, deal with subfields
2488 # browse subfields for this tag (reason for _code_ match)
2489 while(defined $params[$j] && $params[$j] =~ /_code_/) {
2490 last unless defined $params[$j+1];
2492 if $tag == $biblionumbertagfield and
2493 $cgi->param($params[$j]) eq $biblionumbertagsubfield;
2494 #if next param ne subfield, then it was probably empty
2495 #try next param by incrementing j
2496 if($params[$j+1]!~/_subfield_/) {$j++; next; }
2497 my $fkey= $cgi->param($params[$j]);
2498 my $fval= $cgi->param($params[$j+1]);
2499 #check if subfield value not empty and field exists
2500 if($fval ne '' && $newfield) {
2501 $newfield->add_subfields( $fkey => $fval);
2503 elsif($fval ne '') {
2504 $newfield = MARC::Field->new( $tag, $ind1, $ind2, $fkey => $fval );
2508 $i= $j-1; #update i for outer loop accordingly
2510 push @fields, $newfield if ($newfield);
2514 $record->append_fields(@fields);
2518 =head2 TransformMarcToKoha
2520 $result = TransformMarcToKoha( $record, undef, $limit )
2522 Extract data from a MARC bib record into a hashref representing
2523 Koha biblio, biblioitems, and items fields.
2525 If passed an undefined record will log the error and return an empty
2530 sub TransformMarcToKoha {
2531 my ( $record, $frameworkcode, $limit_table ) = @_;
2532 # FIXME Parameter $frameworkcode is obsolete and will be removed
2533 $limit_table //= q{};
2536 if (!defined $record) {
2537 carp('TransformMarcToKoha called with undefined record');
2541 my %tables = ( biblio => 1, biblioitems => 1, items => 1 );
2542 if( $limit_table eq 'items' ) {
2543 %tables = ( items => 1 );
2546 # The next call acknowledges Default as the authoritative framework
2547 # for Koha to MARC mappings.
2548 my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
2549 foreach my $kohafield ( keys %{ $mss } ) {
2550 my ( $table, $column ) = split /[.]/, $kohafield, 2;
2551 next unless $tables{$table};
2552 my $val = TransformMarcToKohaOneField( $kohafield, $record );
2553 next if !defined $val;
2554 my $key = _disambiguate( $table, $column );
2555 $result->{$key} = $val;
2560 =head2 _disambiguate
2562 $newkey = _disambiguate($table, $field);
2564 This is a temporary hack to distinguish between the
2565 following sets of columns when using TransformMarcToKoha.
2567 items.cn_source & biblioitems.cn_source
2568 items.cn_sort & biblioitems.cn_sort
2570 Columns that are currently NOT distinguished (FIXME
2571 due to lack of time to fully test) are:
2573 biblio.notes and biblioitems.notes
2578 FIXME - this is necessary because prefixing each column
2579 name with the table name would require changing lots
2580 of code and templates, and exposing more of the DB
2581 structure than is good to the UI templates, particularly
2582 since biblio and bibloitems may well merge in a future
2583 version. In the future, it would also be good to
2584 separate DB access and UI presentation field names
2590 my ( $table, $column ) = @_;
2591 if ( $column eq "cn_sort" or $column eq "cn_source" ) {
2592 return $table . '.' . $column;
2599 =head2 TransformMarcToKohaOneField
2601 $val = TransformMarcToKohaOneField( 'biblio.title', $marc );
2603 Note: The authoritative Default framework is used implicitly.
2607 sub TransformMarcToKohaOneField {
2608 my ( $kohafield, $marc ) = @_;
2610 my ( @rv, $retval );
2611 my @mss = GetMarcSubfieldStructureFromKohaField($kohafield);
2612 foreach my $fldhash ( @mss ) {
2613 my $tag = $fldhash->{tagfield};
2614 my $sub = $fldhash->{tagsubfield};
2615 foreach my $fld ( $marc->field($tag) ) {
2616 if( $sub eq '@' || $fld->is_control_field ) {
2617 push @rv, $fld->data if $fld->data;
2619 push @rv, grep { $_ } $fld->subfield($sub);
2624 $retval = join ' | ', uniq(@rv);
2626 # Additional polishing for individual kohafields
2627 if( $kohafield =~ /copyrightdate|publicationyear/ ) {
2628 $retval = _adjust_pubyear( $retval );
2634 =head2 _adjust_pubyear
2636 Helper routine for TransformMarcToKohaOneField
2640 sub _adjust_pubyear {
2642 # modify return value to keep only the 1st year found
2643 if( $retval =~ m/c(\d\d\d\d)/ and $1 > 0 ) { # search cYYYY first
2645 } elsif( $retval =~ m/(\d\d\d\d)/ && $1 > 0 ) {
2647 } elsif( $retval =~ m/
2648 (?<year>\d)[-]?[.Xx?]{3}
2649 |(?<year>\d{2})[.Xx?]{2}
2650 |(?<year>\d{3})[.Xx?]
2651 |(?<year>\d)[-]{3}\?
2652 |(?<year>\d\d)[-]{2}\?
2653 |(?<year>\d{3})[-]\?
2654 /xms ) { # the form 198-? occurred in Dutch ISBD rules
2655 my $digits = $+{year};
2656 $retval = $digits * ( 10 ** ( 4 - length($digits) ));
2661 =head2 CountItemsIssued
2663 my $count = CountItemsIssued( $biblionumber );
2667 sub CountItemsIssued {
2668 my ($biblionumber) = @_;
2669 my $dbh = C4::Context->dbh;
2670 my $sth = $dbh->prepare('SELECT COUNT(*) as issuedCount FROM items, issues WHERE items.itemnumber = issues.itemnumber AND items.biblionumber = ?');
2671 $sth->execute($biblionumber);
2672 my $row = $sth->fetchrow_hashref();
2673 return $row->{'issuedCount'};
2678 ModZebra( $biblionumber, $op, $server, $record );
2680 $biblionumber is the biblionumber we want to index
2682 $op is specialUpdate or recordDelete, and is used to know what we want to do
2684 $server is the server that we want to update
2686 $record is the update MARC record if it's available. If it's not supplied
2687 and is needed, it'll be loaded from the database.
2692 ###Accepts a $server variable thus we can use it for biblios authorities or other zebra dbs
2693 my ( $biblionumber, $op, $server, $record ) = @_;
2694 $debug && warn "ModZebra: update requested for: $biblionumber $op $server\n";
2695 if ( C4::Context->preference('SearchEngine') eq 'Elasticsearch' ) {
2697 # TODO abstract to a standard API that'll work for whatever
2698 require Koha::SearchEngine::Elasticsearch::Indexer;
2699 my $indexer = Koha::SearchEngine::Elasticsearch::Indexer->new(
2701 index => $server eq 'biblioserver'
2702 ? $Koha::SearchEngine::BIBLIOS_INDEX
2703 : $Koha::SearchEngine::AUTHORITIES_INDEX
2706 if ( $op eq 'specialUpdate' ) {
2708 $record = GetMarcBiblio({
2709 biblionumber => $biblionumber,
2710 embed_items => 1 });
2712 my $records = [$record];
2713 $indexer->update_index_background( [$biblionumber], [$record] );
2715 elsif ( $op eq 'recordDelete' ) {
2716 $indexer->delete_index_background( [$biblionumber] );
2719 croak "ModZebra called with unknown operation: $op";
2723 my $dbh = C4::Context->dbh;
2725 # true ModZebra commented until indexdata fixes zebraDB crashes (it seems they occur on multiple updates
2727 # replaced by a zebraqueue table, that is filled with ModZebra to run.
2728 # the table is emptied by rebuild_zebra.pl script (using the -z switch)
2729 my $check_sql = "SELECT COUNT(*) FROM zebraqueue
2731 AND biblio_auth_number = ?
2734 my $check_sth = $dbh->prepare_cached($check_sql);
2735 $check_sth->execute( $server, $biblionumber, $op );
2736 my ($count) = $check_sth->fetchrow_array;
2737 $check_sth->finish();
2738 if ( $count == 0 ) {
2739 my $sth = $dbh->prepare("INSERT INTO zebraqueue (biblio_auth_number,server,operation) VALUES(?,?,?)");
2740 $sth->execute( $biblionumber, $server, $op );
2746 =head2 EmbedItemsInMarcBiblio
2748 EmbedItemsInMarcBiblio($marc, $biblionumber, $itemnumbers, $opac);
2750 Given a MARC::Record object containing a bib record,
2751 modify it to include the items attached to it as 9XX
2752 per the bib's MARC framework.
2753 if $itemnumbers is defined, only specified itemnumbers are embedded.
2755 If $opac is true, then opac-relevant suppressions are included.
2759 sub EmbedItemsInMarcBiblio {
2760 my ($marc, $biblionumber, $itemnumbers, $opac) = @_;
2762 carp 'EmbedItemsInMarcBiblio: No MARC record passed';
2766 $itemnumbers = [] unless defined $itemnumbers;
2768 my $frameworkcode = GetFrameworkCode($biblionumber);
2769 _strip_item_fields($marc, $frameworkcode);
2771 # ... and embed the current items
2772 my $dbh = C4::Context->dbh;
2773 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
2774 $sth->execute($biblionumber);
2776 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2778 my $opachiddenitems = $opac
2779 && ( C4::Context->preference('OpacHiddenItems') !~ /^\s*$/ );
2781 while ( my ($itemnumber) = $sth->fetchrow_array ) {
2782 next if @$itemnumbers and not grep { $_ == $itemnumber } @$itemnumbers;
2783 my $i = $opachiddenitems ? C4::Items::GetItem($itemnumber) : undef;
2784 push @items, { itemnumber => $itemnumber, item => $i };
2788 ? C4::Items::GetHiddenItemnumbers( map { $_->{item} } @items )
2790 # Convert to a hash for quick searching
2791 my %hiddenitems = map { $_ => 1 } @hiddenitems;
2792 foreach my $itemnumber ( map { $_->{itemnumber} } @items ) {
2793 next if $hiddenitems{$itemnumber};
2794 my $item_marc = C4::Items::GetMarcItem( $biblionumber, $itemnumber );
2795 push @item_fields, $item_marc->field($itemtag);
2797 $marc->append_fields(@item_fields);
2800 =head1 INTERNAL FUNCTIONS
2802 =head2 _koha_marc_update_bib_ids
2805 _koha_marc_update_bib_ids($record, $frameworkcode, $biblionumber, $biblioitemnumber);
2807 Internal function to add or update biblionumber and biblioitemnumber to
2812 sub _koha_marc_update_bib_ids {
2813 my ( $record, $frameworkcode, $biblionumber, $biblioitemnumber ) = @_;
2815 my ( $biblio_tag, $biblio_subfield ) = GetMarcFromKohaField( "biblio.biblionumber", $frameworkcode );
2816 die qq{No biblionumber tag for framework "$frameworkcode"} unless $biblio_tag;
2817 my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.biblioitemnumber", $frameworkcode );
2818 die qq{No biblioitemnumber tag for framework "$frameworkcode"} unless $biblioitem_tag;
2820 if ( $biblio_tag < 10 ) {
2821 C4::Biblio::UpsertMarcControlField( $record, $biblio_tag, $biblionumber );
2823 C4::Biblio::UpsertMarcSubfield($record, $biblio_tag, $biblio_subfield, $biblionumber);
2825 if ( $biblioitem_tag < 10 ) {
2826 C4::Biblio::UpsertMarcControlField( $record, $biblioitem_tag, $biblioitemnumber );
2828 C4::Biblio::UpsertMarcSubfield($record, $biblioitem_tag, $biblioitem_subfield, $biblioitemnumber);
2832 =head2 _koha_marc_update_biblioitem_cn_sort
2834 _koha_marc_update_biblioitem_cn_sort($marc, $biblioitem, $frameworkcode);
2836 Given a MARC bib record and the biblioitem hash, update the
2837 subfield that contains a copy of the value of biblioitems.cn_sort.
2841 sub _koha_marc_update_biblioitem_cn_sort {
2843 my $biblioitem = shift;
2844 my $frameworkcode = shift;
2846 my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.cn_sort", $frameworkcode );
2847 return unless $biblioitem_tag;
2849 my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
2851 if ( my $field = $marc->field($biblioitem_tag) ) {
2852 $field->delete_subfield( code => $biblioitem_subfield );
2853 if ( $cn_sort ne '' ) {
2854 $field->add_subfields( $biblioitem_subfield => $cn_sort );
2858 # if we get here, no biblioitem tag is present in the MARC record, so
2859 # we'll create it if $cn_sort is not empty -- this would be
2860 # an odd combination of events, however
2862 $marc->insert_grouped_field( MARC::Field->new( $biblioitem_tag, ' ', ' ', $biblioitem_subfield => $cn_sort ) );
2867 =head2 _koha_add_biblio
2869 my ($biblionumber,$error) = _koha_add_biblio($dbh,$biblioitem);
2871 Internal function to add a biblio ($biblio is a hash with the values)
2875 sub _koha_add_biblio {
2876 my ( $dbh, $biblio, $frameworkcode ) = @_;
2880 # set the series flag
2881 unless (defined $biblio->{'serial'}){
2882 $biblio->{'serial'} = 0;
2883 if ( $biblio->{'seriestitle'} ) { $biblio->{'serial'} = 1 }
2886 my $query = "INSERT INTO biblio
2887 SET frameworkcode = ?,
2898 my $sth = $dbh->prepare($query);
2900 $frameworkcode, $biblio->{'author'}, $biblio->{'title'}, $biblio->{'unititle'}, $biblio->{'notes'},
2901 $biblio->{'serial'}, $biblio->{'seriestitle'}, $biblio->{'copyrightdate'}, $biblio->{'abstract'}
2904 my $biblionumber = $dbh->{'mysql_insertid'};
2905 if ( $dbh->errstr ) {
2906 $error .= "ERROR in _koha_add_biblio $query" . $dbh->errstr;
2912 #warn "LEAVING _koha_add_biblio: ".$biblionumber."\n";
2913 return ( $biblionumber, $error );
2916 =head2 _koha_modify_biblio
2918 my ($biblionumber,$error) == _koha_modify_biblio($dbh,$biblio,$frameworkcode);
2920 Internal function for updating the biblio table
2924 sub _koha_modify_biblio {
2925 my ( $dbh, $biblio, $frameworkcode ) = @_;
2930 SET frameworkcode = ?,
2939 WHERE biblionumber = ?
2942 my $sth = $dbh->prepare($query);
2945 $frameworkcode, $biblio->{'author'}, $biblio->{'title'}, $biblio->{'unititle'}, $biblio->{'notes'},
2946 $biblio->{'serial'}, $biblio->{'seriestitle'}, $biblio->{'copyrightdate'}, $biblio->{'abstract'}, $biblio->{'biblionumber'}
2947 ) if $biblio->{'biblionumber'};
2949 if ( $dbh->errstr || !$biblio->{'biblionumber'} ) {
2950 $error .= "ERROR in _koha_modify_biblio $query" . $dbh->errstr;
2953 return ( $biblio->{'biblionumber'}, $error );
2956 =head2 _koha_modify_biblioitem_nonmarc
2958 my ($biblioitemnumber,$error) = _koha_modify_biblioitem_nonmarc( $dbh, $biblioitem );
2962 sub _koha_modify_biblioitem_nonmarc {
2963 my ( $dbh, $biblioitem ) = @_;
2966 # re-calculate the cn_sort, it may have changed
2967 my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
2969 my $query = "UPDATE biblioitems
2970 SET biblionumber = ?,
2976 publicationyear = ?,
2980 collectiontitle = ?,
2982 collectionvolume= ?,
2983 editionstatement= ?,
2984 editionresponsibility = ?,
3000 where biblioitemnumber = ?
3002 my $sth = $dbh->prepare($query);
3004 $biblioitem->{'biblionumber'}, $biblioitem->{'volume'}, $biblioitem->{'number'}, $biblioitem->{'itemtype'},
3005 $biblioitem->{'isbn'}, $biblioitem->{'issn'}, $biblioitem->{'publicationyear'}, $biblioitem->{'publishercode'},
3006 $biblioitem->{'volumedate'}, $biblioitem->{'volumedesc'}, $biblioitem->{'collectiontitle'}, $biblioitem->{'collectionissn'},
3007 $biblioitem->{'collectionvolume'}, $biblioitem->{'editionstatement'}, $biblioitem->{'editionresponsibility'}, $biblioitem->{'illus'},
3008 $biblioitem->{'pages'}, $biblioitem->{'bnotes'}, $biblioitem->{'size'}, $biblioitem->{'place'},
3009 $biblioitem->{'lccn'}, $biblioitem->{'url'}, $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'},
3010 $biblioitem->{'cn_item'}, $biblioitem->{'cn_suffix'}, $cn_sort, $biblioitem->{'totalissues'},
3011 $biblioitem->{'ean'}, $biblioitem->{'agerestriction'}, $biblioitem->{'biblioitemnumber'}
3013 if ( $dbh->errstr ) {
3014 $error .= "ERROR in _koha_modify_biblioitem_nonmarc $query" . $dbh->errstr;
3017 return ( $biblioitem->{'biblioitemnumber'}, $error );
3020 =head2 _koha_add_biblioitem
3022 my ($biblioitemnumber,$error) = _koha_add_biblioitem( $dbh, $biblioitem );
3024 Internal function to add a biblioitem
3028 sub _koha_add_biblioitem {
3029 my ( $dbh, $biblioitem ) = @_;
3032 my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
3033 my $query = "INSERT INTO biblioitems SET
3040 publicationyear = ?,
3044 collectiontitle = ?,
3046 collectionvolume= ?,
3047 editionstatement= ?,
3048 editionresponsibility = ?,
3065 my $sth = $dbh->prepare($query);
3067 $biblioitem->{'biblionumber'}, $biblioitem->{'volume'}, $biblioitem->{'number'}, $biblioitem->{'itemtype'},
3068 $biblioitem->{'isbn'}, $biblioitem->{'issn'}, $biblioitem->{'publicationyear'}, $biblioitem->{'publishercode'},
3069 $biblioitem->{'volumedate'}, $biblioitem->{'volumedesc'}, $biblioitem->{'collectiontitle'}, $biblioitem->{'collectionissn'},
3070 $biblioitem->{'collectionvolume'}, $biblioitem->{'editionstatement'}, $biblioitem->{'editionresponsibility'}, $biblioitem->{'illus'},
3071 $biblioitem->{'pages'}, $biblioitem->{'bnotes'}, $biblioitem->{'size'}, $biblioitem->{'place'},
3072 $biblioitem->{'lccn'}, $biblioitem->{'url'}, $biblioitem->{'biblioitems.cn_source'},
3073 $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'}, $biblioitem->{'cn_suffix'}, $cn_sort,
3074 $biblioitem->{'totalissues'}, $biblioitem->{'ean'}, $biblioitem->{'agerestriction'}
3076 my $bibitemnum = $dbh->{'mysql_insertid'};
3078 if ( $dbh->errstr ) {
3079 $error .= "ERROR in _koha_add_biblioitem $query" . $dbh->errstr;
3083 return ( $bibitemnum, $error );
3086 =head2 _koha_delete_biblio
3088 $error = _koha_delete_biblio($dbh,$biblionumber);
3090 Internal sub for deleting from biblio table -- also saves to deletedbiblio
3092 C<$dbh> - the database handle
3094 C<$biblionumber> - the biblionumber of the biblio to be deleted
3098 # FIXME: add error handling
3100 sub _koha_delete_biblio {
3101 my ( $dbh, $biblionumber ) = @_;
3103 # get all the data for this biblio
3104 my $sth = $dbh->prepare("SELECT * FROM biblio WHERE biblionumber=?");
3105 $sth->execute($biblionumber);
3107 # FIXME There is a transaction in _koha_delete_biblio_metadata
3108 # But actually all the following should be done inside a single transaction
3109 if ( my $data = $sth->fetchrow_hashref ) {
3111 # save the record in deletedbiblio
3112 # find the fields to save
3113 my $query = "INSERT INTO deletedbiblio SET ";
3115 foreach my $temp ( keys %$data ) {
3116 $query .= "$temp = ?,";
3117 push( @bind, $data->{$temp} );
3120 # replace the last , by ",?)"
3122 my $bkup_sth = $dbh->prepare($query);
3123 $bkup_sth->execute(@bind);
3126 _koha_delete_biblio_metadata( $biblionumber );
3129 my $sth2 = $dbh->prepare("DELETE FROM biblio WHERE biblionumber=?");
3130 $sth2->execute($biblionumber);
3131 # update the timestamp (Bugzilla 7146)
3132 $sth2= $dbh->prepare("UPDATE deletedbiblio SET timestamp=NOW() WHERE biblionumber=?");
3133 $sth2->execute($biblionumber);
3140 =head2 _koha_delete_biblioitems
3142 $error = _koha_delete_biblioitems($dbh,$biblioitemnumber);
3144 Internal sub for deleting from biblioitems table -- also saves to deletedbiblioitems
3146 C<$dbh> - the database handle
3147 C<$biblionumber> - the biblioitemnumber of the biblioitem to be deleted
3151 # FIXME: add error handling
3153 sub _koha_delete_biblioitems {
3154 my ( $dbh, $biblioitemnumber ) = @_;
3156 # get all the data for this biblioitem
3157 my $sth = $dbh->prepare("SELECT * FROM biblioitems WHERE biblioitemnumber=?");
3158 $sth->execute($biblioitemnumber);
3160 if ( my $data = $sth->fetchrow_hashref ) {
3162 # save the record in deletedbiblioitems
3163 # find the fields to save
3164 my $query = "INSERT INTO deletedbiblioitems SET ";
3166 foreach my $temp ( keys %$data ) {
3167 $query .= "$temp = ?,";
3168 push( @bind, $data->{$temp} );
3171 # replace the last , by ",?)"
3173 my $bkup_sth = $dbh->prepare($query);
3174 $bkup_sth->execute(@bind);
3177 # delete the biblioitem
3178 my $sth2 = $dbh->prepare("DELETE FROM biblioitems WHERE biblioitemnumber=?");
3179 $sth2->execute($biblioitemnumber);
3180 # update the timestamp (Bugzilla 7146)
3181 $sth2= $dbh->prepare("UPDATE deletedbiblioitems SET timestamp=NOW() WHERE biblioitemnumber=?");
3182 $sth2->execute($biblioitemnumber);
3189 =head2 _koha_delete_biblio_metadata
3191 $error = _koha_delete_biblio_metadata($biblionumber);
3193 C<$biblionumber> - the biblionumber of the biblio metadata to be deleted
3197 sub _koha_delete_biblio_metadata {
3198 my ($biblionumber) = @_;
3200 my $dbh = C4::Context->dbh;
3201 my $schema = Koha::Database->new->schema;
3205 INSERT INTO deletedbiblio_metadata (biblionumber, format, marcflavour, metadata)
3206 SELECT biblionumber, format, marcflavour, metadata FROM biblio_metadata WHERE biblionumber=?
3207 |, undef, $biblionumber );
3208 $dbh->do( q|DELETE FROM biblio_metadata WHERE biblionumber=?|,
3209 undef, $biblionumber );
3214 =head1 UNEXPORTED FUNCTIONS
3216 =head2 ModBiblioMarc
3218 &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3220 Add MARC XML data for a biblio to koha
3222 Function exported, but should NOT be used, unless you really know what you're doing
3227 # pass the MARC::Record to this function, and it will create the records in
3229 my ( $record, $biblionumber, $frameworkcode ) = @_;
3231 carp 'ModBiblioMarc passed an undefined record';
3235 # Clone record as it gets modified
3236 $record = $record->clone();
3237 my $dbh = C4::Context->dbh;
3238 my @fields = $record->fields();
3239 if ( !$frameworkcode ) {
3240 $frameworkcode = "";
3242 my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3243 $sth->execute( $frameworkcode, $biblionumber );
3245 my $encoding = C4::Context->preference("marcflavour");
3247 # deal with UNIMARC field 100 (encoding) : create it if needed & set encoding to unicode
3248 if ( $encoding eq "UNIMARC" ) {
3249 my $defaultlanguage = C4::Context->preference("UNIMARCField100Language");
3250 $defaultlanguage = "fre" if (!$defaultlanguage || length($defaultlanguage) != 3);
3251 my $string = $record->subfield( 100, "a" );
3252 if ( ($string) && ( length( $record->subfield( 100, "a" ) ) == 36 ) ) {
3253 my $f100 = $record->field(100);
3254 $record->delete_field($f100);
3256 $string = POSIX::strftime( "%Y%m%d", localtime );
3258 $string = sprintf( "%-*s", 35, $string );
3259 substr ( $string, 22, 3, $defaultlanguage);
3261 substr( $string, 25, 3, "y50" );
3262 unless ( $record->subfield( 100, "a" ) ) {
3263 $record->insert_fields_ordered( MARC::Field->new( 100, "", "", "a" => $string ) );
3267 #enhancement 5374: update transaction date (005) for marc21/unimarc
3268 if($encoding =~ /MARC21|UNIMARC/) {
3269 my @a= (localtime) [5,4,3,2,1,0]; $a[0]+=1900; $a[1]++;
3270 # YY MM DD HH MM SS (update year and month)
3271 my $f005= $record->field('005');
3272 $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3276 biblionumber => $biblionumber,
3277 format => 'marcxml',
3278 marcflavour => C4::Context->preference('marcflavour'),
3280 $record->as_usmarc; # Bug 20126/10455 This triggers field length calculation
3282 # FIXME To replace with ->find_or_create?
3283 if ( my $m_rs = Koha::Biblio::Metadatas->find($metadata) ) {
3284 $m_rs->metadata( $record->as_xml_record($encoding) );
3287 my $m_rs = Koha::Biblio::Metadata->new($metadata);
3288 $m_rs->metadata( $record->as_xml_record($encoding) );
3291 ModZebra( $biblionumber, "specialUpdate", "biblioserver", $record );
3292 return $biblionumber;
3295 =head2 CountBiblioInOrders
3297 $count = &CountBiblioInOrders( $biblionumber);
3299 This function return count of biblios in orders with $biblionumber
3303 sub CountBiblioInOrders {
3304 my ($biblionumber) = @_;
3305 my $dbh = C4::Context->dbh;
3306 my $query = "SELECT count(*)
3308 WHERE biblionumber=? AND (datecancellationprinted IS NULL OR datecancellationprinted='0000-00-00')";
3309 my $sth = $dbh->prepare($query);
3310 $sth->execute($biblionumber);
3311 my $count = $sth->fetchrow;
3315 =head2 prepare_host_field
3317 $marcfield = prepare_host_field( $hostbiblioitem, $marcflavour );
3318 Generate the host item entry for an analytic child entry
3322 sub prepare_host_field {
3323 my ( $hostbiblio, $marcflavour ) = @_;
3324 $marcflavour ||= C4::Context->preference('marcflavour');
3325 my $host = GetMarcBiblio({ biblionumber => $hostbiblio });
3326 # unfortunately as_string does not 'do the right thing'
3327 # if field returns undef
3331 if ( $marcflavour eq 'MARC21' || $marcflavour eq 'NORMARC' ) {
3332 if ( $field = $host->field('100') || $host->field('110') || $host->field('11') ) {
3333 my $s = $field->as_string('ab');
3338 if ( $field = $host->field('245') ) {
3339 my $s = $field->as_string('a');
3344 if ( $field = $host->field('260') ) {
3345 my $s = $field->as_string('abc');
3350 if ( $field = $host->field('240') ) {
3351 my $s = $field->as_string();
3356 if ( $field = $host->field('022') ) {
3357 my $s = $field->as_string('a');
3362 if ( $field = $host->field('020') ) {
3363 my $s = $field->as_string('a');
3368 if ( $field = $host->field('001') ) {
3369 $sfd{w} = $field->data(),;
3371 $host_field = MARC::Field->new( 773, '0', ' ', %sfd );
3374 elsif ( $marcflavour eq 'UNIMARC' ) {
3376 if ( $field = $host->field('700') || $host->field('710') || $host->field('720') ) {
3377 my $s = $field->as_string('ab');
3383 if ( $field = $host->field('200') ) {
3384 my $s = $field->as_string('a');
3389 #place of publicaton
3390 if ( $field = $host->field('210') ) {
3391 my $s = $field->as_string('a');
3396 #date of publication
3397 if ( $field = $host->field('210') ) {
3398 my $s = $field->as_string('d');
3404 if ( $field = $host->field('205') ) {
3405 my $s = $field->as_string();
3411 if ( $field = $host->field('856') ) {
3412 my $s = $field->as_string('u');
3418 if ( $field = $host->field('011') ) {
3419 my $s = $field->as_string('a');
3425 if ( $field = $host->field('010') ) {
3426 my $s = $field->as_string('a');
3431 if ( $field = $host->field('001') ) {
3432 $sfd{0} = $field->data(),;
3434 $host_field = MARC::Field->new( 461, '0', ' ', %sfd );
3441 =head2 UpdateTotalIssues
3443 UpdateTotalIssues($biblionumber, $increase, [$value])
3445 Update the total issue count for a particular bib record.
3449 =item C<$biblionumber> is the biblionumber of the bib to update
3451 =item C<$increase> is the amount to increase (or decrease) the total issues count by
3453 =item C<$value> is the absolute value that total issues count should be set to. If provided, C<$increase> is ignored.
3459 sub UpdateTotalIssues {
3460 my ($biblionumber, $increase, $value) = @_;
3463 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
3465 carp "UpdateTotalIssues could not get biblio record";
3468 my $biblio = Koha::Biblios->find( $biblionumber );
3470 carp "UpdateTotalIssues could not get datas of biblio";
3473 my $biblioitem = $biblio->biblioitem;
3474 my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField('biblioitems.totalissues', $biblio->frameworkcode);
3475 unless ($totalissuestag) {
3476 return 1; # There is nothing to do
3479 if (defined $value) {
3480 $totalissues = $value;
3482 $totalissues = $biblioitem->totalissues + $increase;
3485 my $field = $record->field($totalissuestag);
3486 if (defined $field) {
3487 $field->update( $totalissuessubfield => $totalissues );
3489 $field = MARC::Field->new($totalissuestag, '0', '0',
3490 $totalissuessubfield => $totalissues);
3491 $record->insert_grouped_field($field);
3494 return ModBiblio($record, $biblionumber, $biblio->frameworkcode);
3499 &RemoveAllNsb($record);
3501 Removes all nsb/nse chars from a record
3508 carp 'RemoveAllNsb called with undefined record';
3512 SetUTF8Flag($record);
3514 foreach my $field ($record->fields()) {
3515 if ($field->is_control_field()) {
3516 $field->update(nsb_clean($field->data()));
3518 my @subfields = $field->subfields();
3520 foreach my $subfield (@subfields) {
3521 push @new_subfields, $subfield->[0] => nsb_clean($subfield->[1]);
3523 if (scalar(@new_subfields) > 0) {
3526 $new_field = MARC::Field->new(
3528 $field->indicator(1),
3529 $field->indicator(2),
3534 warn "error in RemoveAllNsb : $@";
3536 $field->replace_with($new_field);
3552 Koha Development Team <http://koha-community.org/>
3554 Paul POULAIN paul.poulain@free.fr
3556 Joshua Ferraro jmf@liblime.com