Bug 26108: (bug 25855 follow-up) Call after_circ_action hook only if issue exists
[koha.git] / C4 / Biblio.pm
1 package C4::Biblio;
2
3 # Copyright 2000-2002 Katipo Communications
4 # Copyright 2010 BibLibre
5 # Copyright 2011 Equinox Software, Inc.
6 #
7 # This file is part of Koha.
8 #
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.
13 #
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.
18 #
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>.
21
22 use Modern::Perl;
23
24 use vars qw(@ISA @EXPORT);
25 BEGIN {
26     require Exporter;
27     @ISA = qw(Exporter);
28
29     @EXPORT = qw(
30         AddBiblio
31         GetBiblioData
32         GetMarcBiblio
33         GetISBDView
34         GetMarcControlnumber
35         GetMarcNotes
36         GetMarcISBN
37         GetMarcISSN
38         GetMarcSubjects
39         GetMarcAuthors
40         GetMarcSeries
41         GetMarcHosts
42         GetMarcUrls
43         GetUsedMarcStructure
44         GetXmlBiblio
45         GetMarcPrice
46         MungeMarcPrice
47         GetMarcQuantity
48         GetAuthorisedValueDesc
49         GetMarcStructure
50         IsMarcStructureInternal
51         GetMarcFromKohaField
52         GetMarcSubfieldStructureFromKohaField
53         GetFrameworkCode
54         TransformKohaToMarc
55         PrepHostMarcField
56         CountItemsIssued
57         ModBiblio
58         ModZebra
59         UpdateTotalIssues
60         RemoveAllNsb
61         DelBiblio
62         BiblioAutoLink
63         LinkBibHeadingsToAuthorities
64         TransformMarcToKoha
65         TransformHtmlToMarc
66         TransformHtmlToXml
67         prepare_host_field
68     );
69
70     # Internal functions
71     # those functions are exported but should not be used
72     # they are useful in a few circumstances, so they are exported,
73     # but don't use them unless you are a core developer ;-)
74     push @EXPORT, qw(
75       ModBiblioMarc
76     );
77 }
78
79 use Carp;
80 use Try::Tiny;
81
82 use Encode qw( decode is_utf8 );
83 use List::MoreUtils qw( uniq );
84 use MARC::Record;
85 use MARC::File::USMARC;
86 use MARC::File::XML;
87 use POSIX qw(strftime);
88 use Module::Load::Conditional qw(can_load);
89
90 use C4::Koha;
91 use C4::Log;    # logaction
92 use C4::Budgets;
93 use C4::ClassSource;
94 use C4::Charset;
95 use C4::Linker;
96 use C4::OAI::Sets;
97 use C4::Debug;
98
99 use Koha::Caches;
100 use Koha::Authority::Types;
101 use Koha::Acquisition::Currencies;
102 use Koha::Biblio::Metadatas;
103 use Koha::Holds;
104 use Koha::ItemTypes;
105 use Koha::Plugins;
106 use Koha::SearchEngine;
107 use Koha::Libraries;
108 use Koha::Util::MARC;
109
110 use vars qw($debug $cgi_debug);
111
112
113 =head1 NAME
114
115 C4::Biblio - cataloging management functions
116
117 =head1 DESCRIPTION
118
119 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:
120
121 =over 4
122
123 =item 1. in the biblio,biblioitems,items, etc tables, which are limited to a one-to-one mapping to underlying MARC data
124
125 =item 2. as raw MARC in the Zebra index and storage engine
126
127 =item 3. as MARC XML in biblio_metadata.metadata
128
129 =back
130
131 In the 3.0 version of Koha, the authoritative record-level information is in biblio_metadata.metadata
132
133 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.
134
135 =over 4
136
137 =item 1. Compared with MySQL, Zebra is slow to update an index for small data changes -- especially for proc-intensive operations like circulation
138
139 =item 2. Zebra's index has been known to crash and a backup of the data is necessary to rebuild it in such cases
140
141 =back
142
143 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:
144
145 =over 4
146
147 =item 1. Add*/Mod*/Del*/ - high-level external functions suitable for being called from external scripts to manage the collection
148
149 =item 2. _koha_* - low-level internal functions for managing the koha tables
150
151 =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.
152
153 =item 4. Zebra functions used to update the Zebra index
154
155 =item 5. internal helper functions such as char_decode, checkitems, etc. Some of these probably belong in Koha.pm
156
157 =back
158
159 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 :
160
161 =over 4
162
163 =item 1. save datas in biblio and biblioitems table, that gives us a biblionumber and a biblioitemnumber
164
165 =item 2. add the biblionumber and biblioitemnumber into the MARC records
166
167 =item 3. save the marc record
168
169 =back
170
171 =head1 EXPORTED FUNCTIONS
172
173 =head2 AddBiblio
174
175   ($biblionumber,$biblioitemnumber) = AddBiblio($record,$frameworkcode);
176
177 Exported function (core API) for adding a new biblio to koha.
178
179 The first argument is a C<MARC::Record> object containing the
180 bib to add, while the second argument is the desired MARC
181 framework code.
182
183 This function also accepts a third, optional argument: a hashref
184 to additional options.  The only defined option is C<defer_marc_save>,
185 which if present and mapped to a true value, causes C<AddBiblio>
186 to omit the call to save the MARC in C<biblio_metadata.metadata>
187 This option is provided B<only>
188 for the use of scripts such as C<bulkmarcimport.pl> that may need
189 to do some manipulation of the MARC record for item parsing before
190 saving it and which cannot afford the performance hit of saving
191 the MARC record twice.  Consequently, do not use that option
192 unless you can guarantee that C<ModBiblioMarc> will be called.
193
194 =cut
195
196 sub AddBiblio {
197     my $record          = shift;
198     my $frameworkcode   = shift;
199     my $options         = @_ ? shift : undef;
200     my $defer_marc_save = 0;
201     if (!$record) {
202         carp('AddBiblio called with undefined record');
203         return;
204     }
205     if ( defined $options and exists $options->{'defer_marc_save'} and $options->{'defer_marc_save'} ) {
206         $defer_marc_save = 1;
207     }
208
209     if (C4::Context->preference('BiblioAddsAuthorities')) {
210         BiblioAutoLink( $record, $frameworkcode );
211     }
212
213     my ( $biblionumber, $biblioitemnumber, $error );
214     my $dbh = C4::Context->dbh;
215
216     # transform the data into koha-table style data
217     SetUTF8Flag($record);
218     my $olddata = TransformMarcToKoha( $record, $frameworkcode );
219     ( $biblionumber, $error ) = _koha_add_biblio( $dbh, $olddata, $frameworkcode );
220     $olddata->{'biblionumber'} = $biblionumber;
221     ( $biblioitemnumber, $error ) = _koha_add_biblioitem( $dbh, $olddata );
222
223     _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
224
225     # update MARC subfield that stores biblioitems.cn_sort
226     _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
227
228     # now add the record
229     ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
230
231     # update OAI-PMH sets
232     if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
233         C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
234     }
235
236     _after_biblio_action_hooks({ action => 'create', biblio_id => $biblionumber });
237
238     logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
239     return ( $biblionumber, $biblioitemnumber );
240 }
241
242 =head2 ModBiblio
243
244   ModBiblio( $record,$biblionumber,$frameworkcode, $disable_autolink);
245
246 Replace an existing bib record identified by C<$biblionumber>
247 with one supplied by the MARC::Record object C<$record>.  The embedded
248 item, biblioitem, and biblionumber fields from the previous
249 version of the bib record replace any such fields of those tags that
250 are present in C<$record>.  Consequently, ModBiblio() is not
251 to be used to try to modify item records.
252
253 C<$frameworkcode> specifies the MARC framework to use
254 when storing the modified bib record; among other things,
255 this controls how MARC fields get mapped to display columns
256 in the C<biblio> and C<biblioitems> tables, as well as
257 which fields are used to store embedded item, biblioitem,
258 and biblionumber data for indexing.
259
260 Unless C<$disable_autolink> is passed ModBiblio will relink record headings
261 to authorities based on settings in the system preferences. This flag allows
262 us to not relink records when the authority linker is saving modifications.
263
264 Returns 1 on success 0 on failure
265
266 =cut
267
268 sub ModBiblio {
269     my ( $record, $biblionumber, $frameworkcode, $disable_autolink ) = @_;
270     if (!$record) {
271         carp 'No record passed to ModBiblio';
272         return 0;
273     }
274
275     if ( C4::Context->preference("CataloguingLog") ) {
276         my $newrecord = GetMarcBiblio({ biblionumber => $biblionumber });
277         logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio BEFORE=>" . $newrecord->as_formatted );
278     }
279
280     if ( !$disable_autolink && C4::Context->preference('BiblioAddsAuthorities') ) {
281         BiblioAutoLink( $record, $frameworkcode );
282     }
283
284     # Cleaning up invalid fields must be done early or SetUTF8Flag is liable to
285     # throw an exception which probably won't be handled.
286     foreach my $field ($record->fields()) {
287         if (! $field->is_control_field()) {
288             if (scalar($field->subfields()) == 0 || (scalar($field->subfields()) == 1 && $field->subfield('9'))) {
289                 $record->delete_field($field);
290             }
291         }
292     }
293
294     SetUTF8Flag($record);
295     my $dbh = C4::Context->dbh;
296
297     $frameworkcode = "" if !$frameworkcode || $frameworkcode eq "Default"; # XXX
298
299     _strip_item_fields($record, $frameworkcode);
300
301     # update biblionumber and biblioitemnumber in MARC
302     # FIXME - this is assuming a 1 to 1 relationship between
303     # biblios and biblioitems
304     my $sth = $dbh->prepare("select biblioitemnumber from biblioitems where biblionumber=?");
305     $sth->execute($biblionumber);
306     my ($biblioitemnumber) = $sth->fetchrow;
307     $sth->finish();
308     _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
309
310     # load the koha-table data object
311     my $oldbiblio = TransformMarcToKoha( $record, $frameworkcode );
312
313     # update MARC subfield that stores biblioitems.cn_sort
314     _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
315
316     # update the MARC record (that now contains biblio and items) with the new record data
317     &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
318
319     # modify the other koha tables
320     _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
321     _koha_modify_biblioitem_nonmarc( $dbh, $oldbiblio );
322
323     _after_biblio_action_hooks({ action => 'modify', biblio_id => $biblionumber });
324
325     # update OAI-PMH sets
326     if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
327         C4::OAI::Sets::UpdateOAISetsBiblio($biblionumber, $record);
328     }
329
330     return 1;
331 }
332
333 =head2 _strip_item_fields
334
335   _strip_item_fields($record, $frameworkcode)
336
337 Utility routine to remove item tags from a
338 MARC bib.
339
340 =cut
341
342 sub _strip_item_fields {
343     my $record = shift;
344     my $frameworkcode = shift;
345     # get the items before and append them to the biblio before updating the record, atm we just have the biblio
346     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber" );
347
348     # delete any item fields from incoming record to avoid
349     # duplication or incorrect data - use AddItem() or ModItem()
350     # to change items
351     foreach my $field ( $record->field($itemtag) ) {
352         $record->delete_field($field);
353     }
354 }
355
356 =head2 DelBiblio
357
358   my $error = &DelBiblio($biblionumber);
359
360 Exported function (core API) for deleting a biblio in koha.
361 Deletes biblio record from Zebra and Koha tables (biblio & biblioitems)
362 Also backs it up to deleted* tables.
363 Checks to make sure that the biblio has no items attached.
364 return:
365 C<$error> : undef unless an error occurs
366
367 =cut
368
369 sub DelBiblio {
370     my ($biblionumber) = @_;
371
372     my $biblio = Koha::Biblios->find( $biblionumber );
373     return unless $biblio; # Should we throw an exception instead?
374
375     my $dbh = C4::Context->dbh;
376     my $error;    # for error handling
377
378     # First make sure this biblio has no items attached
379     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber=?");
380     $sth->execute($biblionumber);
381     if ( my $itemnumber = $sth->fetchrow ) {
382
383         # Fix this to use a status the template can understand
384         $error .= "This Biblio has items attached, please delete them first before deleting this biblio ";
385     }
386
387     return $error if $error;
388
389     # We delete any existing holds
390     my $holds = $biblio->holds;
391     while ( my $hold = $holds->next ) {
392         $hold->cancel;
393     }
394
395     # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
396     # for at least 2 reasons :
397     # - if something goes wrong, the biblio may be deleted from Koha but not from zebra
398     #   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)
399     ModZebra( $biblionumber, "recordDelete", "biblioserver" );
400
401     # delete biblioitems and items from Koha tables and save in deletedbiblioitems,deleteditems
402     $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
403     $sth->execute($biblionumber);
404     while ( my $biblioitemnumber = $sth->fetchrow ) {
405
406         # delete this biblioitem
407         $error = _koha_delete_biblioitems( $dbh, $biblioitemnumber );
408         return $error if $error;
409     }
410
411
412     # delete biblio from Koha tables and save in deletedbiblio
413     # must do this *after* _koha_delete_biblioitems, otherwise
414     # delete cascade will prevent deletedbiblioitems rows
415     # from being generated by _koha_delete_biblioitems
416     $error = _koha_delete_biblio( $dbh, $biblionumber );
417
418     _after_biblio_action_hooks({ action => 'delete', biblio_id => $biblionumber });
419
420     logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
421
422     return;
423 }
424
425
426 =head2 BiblioAutoLink
427
428   my $headings_linked = BiblioAutoLink($record, $frameworkcode)
429
430 Automatically links headings in a bib record to authorities.
431
432 Returns the number of headings changed
433
434 =cut
435
436 sub BiblioAutoLink {
437     my $record        = shift;
438     my $frameworkcode = shift;
439     if (!$record) {
440         carp('Undefined record passed to BiblioAutoLink');
441         return 0;
442     }
443     my ( $num_headings_changed, %results );
444
445     my $linker_module =
446       "C4::Linker::" . ( C4::Context->preference("LinkerModule") || 'Default' );
447     unless ( can_load( modules => { $linker_module => undef } ) ) {
448         $linker_module = 'C4::Linker::Default';
449         unless ( can_load( modules => { $linker_module => undef } ) ) {
450             return 0;
451         }
452     }
453
454     my $linker = $linker_module->new(
455         { 'options' => C4::Context->preference("LinkerOptions") } );
456     my ( $headings_changed, undef ) =
457       LinkBibHeadingsToAuthorities( $linker, $record, $frameworkcode, C4::Context->preference("CatalogModuleRelink") || '' );
458     # By default we probably don't want to relink things when cataloging
459     return $headings_changed;
460 }
461
462 =head2 LinkBibHeadingsToAuthorities
463
464   my $num_headings_changed, %results = LinkBibHeadingsToAuthorities($linker, $marc, $frameworkcode, [$allowrelink]);
465
466 Links bib headings to authority records by checking
467 each authority-controlled field in the C<MARC::Record>
468 object C<$marc>, looking for a matching authority record,
469 and setting the linking subfield $9 to the ID of that
470 authority record.  
471
472 If $allowrelink is false, existing authids will never be
473 replaced, regardless of the values of LinkerKeepStale and
474 LinkerRelink.
475
476 Returns the number of heading links changed in the
477 MARC record.
478
479 =cut
480
481 sub LinkBibHeadingsToAuthorities {
482     my $linker        = shift;
483     my $bib           = shift;
484     my $frameworkcode = shift;
485     my $allowrelink = shift;
486     my %results;
487     if (!$bib) {
488         carp 'LinkBibHeadingsToAuthorities called on undefined bib record';
489         return ( 0, {});
490     }
491     require C4::Heading;
492     require C4::AuthoritiesMarc;
493
494     $allowrelink = 1 unless defined $allowrelink;
495     my $num_headings_changed = 0;
496     foreach my $field ( $bib->fields() ) {
497         my $heading = C4::Heading->new_from_field( $field, $frameworkcode );
498         next unless defined $heading;
499
500         # check existing $9
501         my $current_link = $field->subfield('9');
502
503         if ( defined $current_link && (!$allowrelink || !C4::Context->preference('LinkerRelink')) )
504         {
505             $results{'linked'}->{ $heading->display_form() }++;
506             next;
507         }
508
509         my ( $authid, $fuzzy, $match_count ) = $linker->get_link($heading);
510         if ($authid) {
511             $results{ $fuzzy ? 'fuzzy' : 'linked' }
512               ->{ $heading->display_form() }++;
513             next if defined $current_link and $current_link == $authid;
514
515             $field->delete_subfield( code => '9' ) if defined $current_link;
516             $field->add_subfields( '9', $authid );
517             $num_headings_changed++;
518         }
519         else {
520             if ( defined $current_link
521                 && (!$allowrelink || C4::Context->preference('LinkerKeepStale')) )
522             {
523                 $results{'fuzzy'}->{ $heading->display_form() }++;
524             }
525             elsif ( C4::Context->preference('AutoCreateAuthorities') ) {
526                 if ( _check_valid_auth_link( $current_link, $field ) ) {
527                     $results{'linked'}->{ $heading->display_form() }++;
528                 }
529                 elsif ( !$match_count ) {
530                     my $authority_type = Koha::Authority::Types->find( $heading->auth_type() );
531                     my $marcrecordauth = MARC::Record->new();
532                     if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
533                         $marcrecordauth->leader('     nz  a22     o  4500');
534                         SetMarcUnicodeFlag( $marcrecordauth, 'MARC21' );
535                     }
536                     $field->delete_subfield( code => '9' )
537                       if defined $current_link;
538                     my @auth_subfields;
539                     foreach my $subfield ( $field->subfields() ){
540                         if ( $subfield->[0] =~ /[A-z]/
541                             && C4::Heading::valid_heading_subfield(
542                                 $field->tag, $subfield->[0] )
543                            ){
544                             push @auth_subfields, $subfield->[0] => $subfield->[1];
545                         }
546                     }
547                     # Bib headings contain some ending punctuation that should NOT
548                     # be included in the authority record. Strip those before creation
549                     next unless @auth_subfields; # Don't try to create a record if we have no fields;
550                     my $last_sub = pop @auth_subfields;
551                     $last_sub =~ s/[\s]*[,.:=;!%\/][\s]*$//;
552                     push @auth_subfields, $last_sub;
553                     my $authfield = MARC::Field->new( $authority_type->auth_tag_to_report, '', '', @auth_subfields );
554                     $marcrecordauth->insert_fields_ordered($authfield);
555
556 # bug 2317: ensure new authority knows it's using UTF-8; currently
557 # only need to do this for MARC21, as MARC::Record->as_xml_record() handles
558 # automatically for UNIMARC (by not transcoding)
559 # FIXME: AddAuthority() instead should simply explicitly require that the MARC::Record
560 # use UTF-8, but as of 2008-08-05, did not want to introduce that kind
561 # of change to a core API just before the 3.0 release.
562
563                     if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
564                         my $userenv = C4::Context->userenv;
565                         my $library;
566                         if ( $userenv && $userenv->{'branch'} ) {
567                             $library = Koha::Libraries->find( $userenv->{'branch'} );
568                         }
569                         $marcrecordauth->insert_fields_ordered(
570                             MARC::Field->new(
571                                 '667', '', '',
572                                 'a' => "Machine generated authority record."
573                             )
574                         );
575                         my $cite =
576                             $bib->author() . ", "
577                           . $bib->title_proper() . ", "
578                           . $bib->publication_date() . " ";
579                         $cite =~ s/^[\s\,]*//;
580                         $cite =~ s/[\s\,]*$//;
581                         $cite =
582                             "Work cat.: ("
583                           . ( $library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') ) . ")"
584                           . $bib->subfield( '999', 'c' ) . ": "
585                           . $cite;
586                         $marcrecordauth->insert_fields_ordered(
587                             MARC::Field->new( '670', '', '', 'a' => $cite ) );
588                     }
589
590            #          warn "AUTH RECORD ADDED : ".$marcrecordauth->as_formatted;
591
592                     $authid =
593                       C4::AuthoritiesMarc::AddAuthority( $marcrecordauth, '',
594                         $heading->auth_type() );
595                     $field->add_subfields( '9', $authid );
596                     $num_headings_changed++;
597                     $linker->update_cache($heading, $authid);
598                     $results{'added'}->{ $heading->display_form() }++;
599                 }
600             }
601             elsif ( defined $current_link ) {
602                 if ( _check_valid_auth_link( $current_link, $field ) ) {
603                     $results{'linked'}->{ $heading->display_form() }++;
604                 }
605                 else {
606                     $field->delete_subfield( code => '9' );
607                     $num_headings_changed++;
608                     $results{'unlinked'}->{ $heading->display_form() }++;
609                 }
610             }
611             else {
612                 $results{'unlinked'}->{ $heading->display_form() }++;
613             }
614         }
615
616     }
617     return $num_headings_changed, \%results;
618 }
619
620 =head2 _check_valid_auth_link
621
622     if ( _check_valid_auth_link($authid, $field) ) {
623         ...
624     }
625
626 Check whether the specified heading-auth link is valid without reference
627 to Zebra. Ideally this code would be in C4::Heading, but that won't be
628 possible until we have de-cycled C4::AuthoritiesMarc, so this is the
629 safest place.
630
631 =cut
632
633 sub _check_valid_auth_link {
634     my ( $authid, $field ) = @_;
635     require C4::AuthoritiesMarc;
636
637     my $authorized_heading =
638       C4::AuthoritiesMarc::GetAuthorizedHeading( { 'authid' => $authid } ) || '';
639    return ($field->as_string('abcdefghijklmnopqrstuvwxyz') eq $authorized_heading);
640 }
641
642 =head2 GetBiblioData
643
644   $data = &GetBiblioData($biblionumber);
645
646 Returns information about the book with the given biblionumber.
647 C<&GetBiblioData> returns a reference-to-hash. The keys are the fields in
648 the C<biblio> and C<biblioitems> tables in the
649 Koha database.
650
651 In addition, C<$data-E<gt>{subject}> is the list of the book's
652 subjects, separated by C<" , "> (space, comma, space).
653 If there are multiple biblioitems with the given biblionumber, only
654 the first one is considered.
655
656 =cut
657
658 sub GetBiblioData {
659     my ($bibnum) = @_;
660     my $dbh = C4::Context->dbh;
661
662     my $query = " SELECT * , biblioitems.notes AS bnotes, itemtypes.notforloan as bi_notforloan, biblio.notes
663             FROM biblio
664             LEFT JOIN biblioitems ON biblio.biblionumber = biblioitems.biblionumber
665             LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
666             WHERE biblio.biblionumber = ?";
667
668     my $sth = $dbh->prepare($query);
669     $sth->execute($bibnum);
670     my $data;
671     $data = $sth->fetchrow_hashref;
672     $sth->finish;
673
674     return ($data);
675 }    # sub GetBiblioData
676
677 =head2 GetISBDView 
678
679   $isbd = &GetISBDView({
680       'record'    => $marc_record,
681       'template'  => $interface, # opac/intranet
682       'framework' => $framework,
683   });
684
685 Return the ISBD view which can be included in opac and intranet
686
687 =cut
688
689 sub GetISBDView {
690     my ( $params ) = @_;
691
692     # Expecting record WITH items.
693     my $record    = $params->{record};
694     return unless defined $record;
695
696     my $template  = $params->{template} // q{};
697     my $sysprefname = $template eq 'opac' ? 'opacisbd' : 'isbd';
698     my $framework = $params->{framework};
699     my $itemtype  = $framework;
700     my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch" );
701     my $tagslib = GetMarcStructure( 1, $itemtype, { unsafe => 1 } );
702
703     my $ISBD = C4::Context->preference($sysprefname);
704     my $bloc = $ISBD;
705     my $res;
706     my $blocres;
707
708     foreach my $isbdfield ( split( /#/, $bloc ) ) {
709
710         #         $isbdfield= /(.?.?.?)/;
711         $isbdfield =~ /(\d\d\d)([^\|])?\|(.*)\|(.*)\|(.*)/;
712         my $fieldvalue = $1 || 0;
713         my $subfvalue  = $2 || "";
714         my $textbefore = $3;
715         my $analysestring = $4;
716         my $textafter     = $5;
717
718         #         warn "==> $1 / $2 / $3 / $4";
719         #         my $fieldvalue=substr($isbdfield,0,3);
720         if ( $fieldvalue > 0 ) {
721             my $hasputtextbefore = 0;
722             my @fieldslist       = $record->field($fieldvalue);
723             @fieldslist = sort { $a->subfield($holdingbrtagsubf) cmp $b->subfield($holdingbrtagsubf) } @fieldslist if ( $fieldvalue eq $holdingbrtagf );
724
725             #         warn "ERROR IN ISBD DEFINITION at : $isbdfield" unless $fieldvalue;
726             #             warn "FV : $fieldvalue";
727             if ( $subfvalue ne "" ) {
728                 # OPAC hidden subfield
729                 next
730                   if ( ( $template eq 'opac' )
731                     && ( $tagslib->{$fieldvalue}->{$subfvalue}->{'hidden'} || 0 ) > 0 );
732                 foreach my $field (@fieldslist) {
733                     foreach my $subfield ( $field->subfield($subfvalue) ) {
734                         my $calculated = $analysestring;
735                         my $tag        = $field->tag();
736                         if ( $tag < 10 ) {
737                         } else {
738                             my $subfieldvalue = GetAuthorisedValueDesc( $tag, $subfvalue, $subfield, '', $tagslib );
739                             my $tagsubf = $tag . $subfvalue;
740                             $calculated =~ s/\{(.?.?.?.?)$tagsubf(.*?)\}/$1$subfieldvalue$2\{$1$tagsubf$2\}/g;
741                             if ( $template eq "opac" ) { $calculated =~ s#/cgi-bin/koha/[^/]+/([^.]*.pl\?.*)$#opac-$1#g; }
742
743                             # field builded, store the result
744                             if ( $calculated && !$hasputtextbefore ) {    # put textbefore if not done
745                                 $blocres .= $textbefore;
746                                 $hasputtextbefore = 1;
747                             }
748
749                             # remove punctuation at start
750                             $calculated =~ s/^( |;|:|\.|-)*//g;
751                             $blocres .= $calculated;
752
753                         }
754                     }
755                 }
756                 $blocres .= $textafter if $hasputtextbefore;
757             } else {
758                 foreach my $field (@fieldslist) {
759                     my $calculated = $analysestring;
760                     my $tag        = $field->tag();
761                     if ( $tag < 10 ) {
762                     } else {
763                         my @subf = $field->subfields;
764                         for my $i ( 0 .. $#subf ) {
765                             my $valuecode     = $subf[$i][1];
766                             my $subfieldcode  = $subf[$i][0];
767                             # OPAC hidden subfield
768                             next
769                               if ( ( $template eq 'opac' )
770                                 && ( $tagslib->{$fieldvalue}->{$subfieldcode}->{'hidden'} || 0 ) > 0 );
771                             my $subfieldvalue = GetAuthorisedValueDesc( $tag, $subf[$i][0], $subf[$i][1], '', $tagslib );
772                             my $tagsubf       = $tag . $subfieldcode;
773
774                             $calculated =~ s/                  # replace all {{}} codes by the value code.
775                                   \{\{$tagsubf\}\} # catch the {{actualcode}}
776                                 /
777                                   $valuecode     # replace by the value code
778                                /gx;
779
780                             $calculated =~ s/\{(.?.?.?.?)$tagsubf(.*?)\}/$1$subfieldvalue$2\{$1$tagsubf$2\}/g;
781                             if ( $template eq "opac" ) { $calculated =~ s#/cgi-bin/koha/[^/]+/([^.]*.pl\?.*)$#opac-$1#g; }
782                         }
783
784                         # field builded, store the result
785                         if ( $calculated && !$hasputtextbefore ) {    # put textbefore if not done
786                             $blocres .= $textbefore;
787                             $hasputtextbefore = 1;
788                         }
789
790                         # remove punctuation at start
791                         $calculated =~ s/^( |;|:|\.|-)*//g;
792                         $blocres .= $calculated;
793                     }
794                 }
795                 $blocres .= $textafter if $hasputtextbefore;
796             }
797         } else {
798             $blocres .= $isbdfield;
799         }
800     }
801     $res .= $blocres;
802
803     $res =~ s/\{(.*?)\}//g;
804     $res =~ s/\\n/\n/g;
805     $res =~ s/\n/<br\/>/g;
806
807     # remove empty ()
808     $res =~ s/\(\)//g;
809
810     return $res;
811 }
812
813 =head1 FUNCTIONS FOR HANDLING MARC MANAGEMENT
814
815 =head2 IsMarcStructureInternal
816
817     my $tagslib = C4::Biblio::GetMarcStructure();
818     for my $tag ( sort keys %$tagslib ) {
819         next unless $tag;
820         for my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
821             next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
822         }
823         # Process subfield
824     }
825
826 GetMarcStructure creates keys (lib, tab, mandatory, repeatable, important) for a display purpose.
827 These different values should not be processed as valid subfields.
828
829 =cut
830
831 sub IsMarcStructureInternal {
832     my ( $subfield ) = @_;
833     return ref $subfield ? 0 : 1;
834 }
835
836 =head2 GetMarcStructure
837
838   $res = GetMarcStructure($forlibrarian, $frameworkcode, [ $params ]);
839
840 Returns a reference to a big hash of hash, with the Marc structure for the given frameworkcode
841 $forlibrarian  :if set to 1, the MARC descriptions are the librarians ones, otherwise it's the public (OPAC) ones
842 $frameworkcode : the framework code to read
843 $params allows you to pass { unsafe => 1 } for better performance.
844
845 Note: If you call GetMarcStructure with unsafe => 1, do not modify or
846 even autovivify its contents. It is a cached/shared data structure. Your
847 changes c/would be passed around in subsequent calls.
848
849 =cut
850
851 sub GetMarcStructure {
852     my ( $forlibrarian, $frameworkcode, $params ) = @_;
853     $frameworkcode = "" unless $frameworkcode;
854
855     $forlibrarian = $forlibrarian ? 1 : 0;
856     my $unsafe = ($params && $params->{unsafe})? 1: 0;
857     my $cache = Koha::Caches->get_instance();
858     my $cache_key = "MarcStructure-$forlibrarian-$frameworkcode";
859     my $cached = $cache->get_from_cache($cache_key, { unsafe => $unsafe });
860     return $cached if $cached;
861
862     my $dbh = C4::Context->dbh;
863     my $sth = $dbh->prepare(
864         "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable,important,ind1_defaultvalue,ind2_defaultvalue
865         FROM marc_tag_structure 
866         WHERE frameworkcode=? 
867         ORDER BY tagfield"
868     );
869     $sth->execute($frameworkcode);
870     my ( $liblibrarian, $libopac, $tag, $res, $tab, $mandatory, $repeatable, $important, $ind1_defaultvalue, $ind2_defaultvalue );
871
872     while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable, $important, $ind1_defaultvalue, $ind2_defaultvalue ) = $sth->fetchrow ) {
873         $res->{$tag}->{lib}        = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
874         $res->{$tag}->{tab}        = "";
875         $res->{$tag}->{mandatory}  = $mandatory;
876         $res->{$tag}->{important}  = $important;
877         $res->{$tag}->{repeatable} = $repeatable;
878     $res->{$tag}->{ind1_defaultvalue} = $ind1_defaultvalue;
879     $res->{$tag}->{ind2_defaultvalue} = $ind2_defaultvalue;
880     }
881
882     $sth = $dbh->prepare(
883         "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab,mandatory,repeatable,authorised_value,authtypecode,value_builder,kohafield,seealso,hidden,isurl,link,defaultvalue,maxlength,important
884          FROM   marc_subfield_structure 
885          WHERE  frameworkcode=? 
886          ORDER BY tagfield,tagsubfield
887         "
888     );
889
890     $sth->execute($frameworkcode);
891
892     my $subfield;
893     my $authorised_value;
894     my $authtypecode;
895     my $value_builder;
896     my $kohafield;
897     my $seealso;
898     my $hidden;
899     my $isurl;
900     my $link;
901     my $defaultvalue;
902     my $maxlength;
903
904     while (
905         (   $tag,          $subfield,      $liblibrarian, $libopac, $tab,    $mandatory, $repeatable, $authorised_value,
906             $authtypecode, $value_builder, $kohafield,    $seealso, $hidden, $isurl,     $link,       $defaultvalue,
907             $maxlength, $important
908         )
909         = $sth->fetchrow
910       ) {
911         $res->{$tag}->{$subfield}->{lib}              = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
912         $res->{$tag}->{$subfield}->{tab}              = $tab;
913         $res->{$tag}->{$subfield}->{mandatory}        = $mandatory;
914         $res->{$tag}->{$subfield}->{important}        = $important;
915         $res->{$tag}->{$subfield}->{repeatable}       = $repeatable;
916         $res->{$tag}->{$subfield}->{authorised_value} = $authorised_value;
917         $res->{$tag}->{$subfield}->{authtypecode}     = $authtypecode;
918         $res->{$tag}->{$subfield}->{value_builder}    = $value_builder;
919         $res->{$tag}->{$subfield}->{kohafield}        = $kohafield;
920         $res->{$tag}->{$subfield}->{seealso}          = $seealso;
921         $res->{$tag}->{$subfield}->{hidden}           = $hidden;
922         $res->{$tag}->{$subfield}->{isurl}            = $isurl;
923         $res->{$tag}->{$subfield}->{'link'}           = $link;
924         $res->{$tag}->{$subfield}->{defaultvalue}     = $defaultvalue;
925         $res->{$tag}->{$subfield}->{maxlength}        = $maxlength;
926     }
927
928     $cache->set_in_cache($cache_key, $res);
929     return $res;
930 }
931
932 =head2 GetUsedMarcStructure
933
934 The same function as GetMarcStructure except it just takes field
935 in tab 0-9. (used field)
936
937   my $results = GetUsedMarcStructure($frameworkcode);
938
939 C<$results> is a ref to an array which each case contains a ref
940 to a hash which each keys is the columns from marc_subfield_structure
941
942 C<$frameworkcode> is the framework code. 
943
944 =cut
945
946 sub GetUsedMarcStructure {
947     my $frameworkcode = shift || '';
948     my $query = q{
949         SELECT *
950         FROM   marc_subfield_structure
951         WHERE   tab > -1 
952             AND frameworkcode = ?
953         ORDER BY tagfield, tagsubfield
954     };
955     my $sth = C4::Context->dbh->prepare($query);
956     $sth->execute($frameworkcode);
957     return $sth->fetchall_arrayref( {} );
958 }
959
960 =pod
961
962 =head2 GetMarcSubfieldStructure
963
964   my $structure = GetMarcSubfieldStructure($frameworkcode, [$params]);
965
966 Returns a reference to hash representing MARC subfield structure
967 for framework with framework code C<$frameworkcode>, C<$params> is
968 optional and may contain additional options.
969
970 =over 4
971
972 =item C<$frameworkcode>
973
974 The framework code.
975
976 =item C<$params>
977
978 An optional hash reference with additional options.
979 The following options are supported:
980
981 =over 4
982
983 =item unsafe
984
985 Pass { unsafe => 1 } do disable cached object cloning,
986 and instead get a shared reference, resulting in better
987 performance (but care must be taken so that retured object
988 is never modified).
989
990 Note: If you call GetMarcSubfieldStructure with unsafe => 1, do not modify or
991 even autovivify its contents. It is a cached/shared data structure. Your
992 changes would be passed around in subsequent calls.
993
994 =back
995
996 =back
997
998 =cut
999
1000 sub GetMarcSubfieldStructure {
1001     my ( $frameworkcode, $params ) = @_;
1002
1003     $frameworkcode //= '';
1004
1005     my $cache     = Koha::Caches->get_instance();
1006     my $cache_key = "MarcSubfieldStructure-$frameworkcode";
1007     my $cached  = $cache->get_from_cache($cache_key, { unsafe => ($params && $params->{unsafe}) });
1008     return $cached if $cached;
1009
1010     my $dbh = C4::Context->dbh;
1011     # We moved to selectall_arrayref since selectall_hashref does not
1012     # keep duplicate mappings on kohafield (like place in 260 vs 264)
1013     my $subfield_aref = $dbh->selectall_arrayref( q|
1014         SELECT *
1015         FROM marc_subfield_structure
1016         WHERE frameworkcode = ?
1017         AND kohafield > ''
1018         ORDER BY frameworkcode,tagfield,tagsubfield
1019     |, { Slice => {} }, $frameworkcode );
1020     # Now map the output to a hash structure
1021     my $subfield_structure = {};
1022     foreach my $row ( @$subfield_aref ) {
1023         push @{ $subfield_structure->{ $row->{kohafield} }}, $row;
1024     }
1025     $cache->set_in_cache( $cache_key, $subfield_structure );
1026     return $subfield_structure;
1027 }
1028
1029 =head2 GetMarcFromKohaField
1030
1031     ( $field,$subfield ) = GetMarcFromKohaField( $kohafield );
1032     @fields = GetMarcFromKohaField( $kohafield );
1033     $field = GetMarcFromKohaField( $kohafield );
1034
1035     Returns the MARC fields & subfields mapped to $kohafield.
1036     Since the Default framework is considered as authoritative for such
1037     mappings, the former frameworkcode parameter is obsoleted.
1038
1039     In list context all mappings are returned; there can be multiple
1040     mappings. Note that in the above example you could miss a second
1041     mappings in the first call.
1042     In scalar context only the field tag of the first mapping is returned.
1043
1044 =cut
1045
1046 sub GetMarcFromKohaField {
1047     my ( $kohafield ) = @_;
1048     return unless $kohafield;
1049     # The next call uses the Default framework since it is AUTHORITATIVE
1050     # for all Koha to MARC mappings.
1051     my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
1052     my @retval;
1053     foreach( @{ $mss->{$kohafield} } ) {
1054         push @retval, $_->{tagfield}, $_->{tagsubfield};
1055     }
1056     return wantarray ? @retval : ( @retval ? $retval[0] : undef );
1057 }
1058
1059 =head2 GetMarcSubfieldStructureFromKohaField
1060
1061     my $str = GetMarcSubfieldStructureFromKohaField( $kohafield );
1062
1063     Returns marc subfield structure information for $kohafield.
1064     The Default framework is used, since it is authoritative for kohafield
1065     mappings.
1066     In list context returns a list of all hashrefs, since there may be
1067     multiple mappings. In scalar context the first hashref is returned.
1068
1069 =cut
1070
1071 sub GetMarcSubfieldStructureFromKohaField {
1072     my ( $kohafield ) = @_;
1073
1074     return unless $kohafield;
1075
1076     # The next call uses the Default framework since it is AUTHORITATIVE
1077     # for all Koha to MARC mappings.
1078     my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
1079     return unless $mss->{$kohafield};
1080     return wantarray ? @{$mss->{$kohafield}} : $mss->{$kohafield}->[0];
1081 }
1082
1083 =head2 GetMarcBiblio
1084
1085   my $record = GetMarcBiblio({
1086       biblionumber => $biblionumber,
1087       embed_items  => $embeditems,
1088       opac         => $opac,
1089       borcat       => $patron_category });
1090
1091 Returns MARC::Record representing a biblio record, or C<undef> if the
1092 biblionumber doesn't exist.
1093
1094 Both embed_items and opac are optional.
1095 If embed_items is passed and is 1, items are embedded.
1096 If opac is passed and is 1, the record is filtered as needed.
1097
1098 =over 4
1099
1100 =item C<$biblionumber>
1101
1102 the biblionumber
1103
1104 =item C<$embeditems>
1105
1106 set to true to include item information.
1107
1108 =item C<$opac>
1109
1110 set to true to make the result suited for OPAC view. This causes things like
1111 OpacHiddenItems to be applied.
1112
1113 =item C<$borcat>
1114
1115 If the OpacHiddenItemsExceptions system preference is set, this patron category
1116 can be used to make visible OPAC items which would be normally hidden.
1117 It only makes sense in combination both embed_items and opac values true.
1118
1119 =back
1120
1121 =cut
1122
1123 sub GetMarcBiblio {
1124     my ($params) = @_;
1125
1126     if (not defined $params) {
1127         carp 'GetMarcBiblio called without parameters';
1128         return;
1129     }
1130
1131     my $biblionumber = $params->{biblionumber};
1132     my $embeditems   = $params->{embed_items} || 0;
1133     my $opac         = $params->{opac} || 0;
1134     my $borcat       = $params->{borcat} // q{};
1135
1136     if (not defined $biblionumber) {
1137         carp 'GetMarcBiblio called with undefined biblionumber';
1138         return;
1139     }
1140
1141     my $dbh          = C4::Context->dbh;
1142     my $sth          = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=? ");
1143     $sth->execute($biblionumber);
1144     my $row     = $sth->fetchrow_hashref;
1145     my $biblioitemnumber = $row->{'biblioitemnumber'};
1146     my $marcxml = GetXmlBiblio( $biblionumber );
1147     $marcxml = StripNonXmlChars( $marcxml );
1148     my $frameworkcode = GetFrameworkCode($biblionumber);
1149     MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
1150     my $record = MARC::Record->new();
1151
1152     if ($marcxml) {
1153         $record = eval {
1154             MARC::Record::new_from_xml( $marcxml, "UTF-8",
1155                 C4::Context->preference('marcflavour') );
1156         };
1157         if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
1158         return unless $record;
1159
1160         C4::Biblio::_koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber,
1161             $biblioitemnumber );
1162         C4::Biblio::EmbedItemsInMarcBiblio({
1163             marc_record  => $record,
1164             biblionumber => $biblionumber,
1165             opac         => $opac,
1166             borcat       => $borcat })
1167           if ($embeditems);
1168
1169         return $record;
1170     }
1171     else {
1172         return;
1173     }
1174 }
1175
1176 =head2 GetXmlBiblio
1177
1178   my $marcxml = GetXmlBiblio($biblionumber);
1179
1180 Returns biblio_metadata.metadata/marcxml of the biblionumber passed in parameter.
1181 The XML should only contain biblio information (item information is no longer stored in marcxml field)
1182
1183 =cut
1184
1185 sub GetXmlBiblio {
1186     my ($biblionumber) = @_;
1187     my $dbh = C4::Context->dbh;
1188     return unless $biblionumber;
1189     my ($marcxml) = $dbh->selectrow_array(
1190         q|
1191         SELECT metadata
1192         FROM biblio_metadata
1193         WHERE biblionumber=?
1194             AND format='marcxml'
1195             AND `schema`=?
1196     |, undef, $biblionumber, C4::Context->preference('marcflavour')
1197     );
1198     return $marcxml;
1199 }
1200
1201 =head2 GetMarcPrice
1202
1203 return the prices in accordance with the Marc format.
1204
1205 returns 0 if no price found
1206 returns undef if called without a marc record or with
1207 an unrecognized marc format
1208
1209 =cut
1210
1211 sub GetMarcPrice {
1212     my ( $record, $marcflavour ) = @_;
1213     if (!$record) {
1214         carp 'GetMarcPrice called on undefined record';
1215         return;
1216     }
1217
1218     my @listtags;
1219     my $subfield;
1220     
1221     if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
1222         @listtags = ('345', '020');
1223         $subfield="c";
1224     } elsif ( $marcflavour eq "UNIMARC" ) {
1225         @listtags = ('345', '010');
1226         $subfield="d";
1227     } else {
1228         return;
1229     }
1230     
1231     for my $field ( $record->field(@listtags) ) {
1232         for my $subfield_value  ($field->subfield($subfield)){
1233             #check value
1234             $subfield_value = MungeMarcPrice( $subfield_value );
1235             return $subfield_value if ($subfield_value);
1236         }
1237     }
1238     return 0; # no price found
1239 }
1240
1241 =head2 MungeMarcPrice
1242
1243 Return the best guess at what the actual price is from a price field.
1244
1245 =cut
1246
1247 sub MungeMarcPrice {
1248     my ( $price ) = @_;
1249     return unless ( $price =~ m/\d/ ); ## No digits means no price.
1250     # Look for the currency symbol and the normalized code of the active currency, if it's there,
1251     my $active_currency = Koha::Acquisition::Currencies->get_active;
1252     my $symbol = $active_currency->symbol;
1253     my $isocode = $active_currency->isocode;
1254     $isocode = $active_currency->currency unless defined $isocode;
1255     my $localprice;
1256     if ( $symbol ) {
1257         my @matches =($price=~ /
1258             \s?
1259             (                          # start of capturing parenthesis
1260             (?:
1261             (?:[\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'
1262             |(?:\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'
1263             )
1264             \s?\p{Sc}?\s?              # followed or not by a whitespace. \p{Sc}?\s? are for cases like '25$ USD'
1265             (?:
1266             (?:[\p{Sc}\p{L}\/.]){1,4}  # followed by same block as symbol block
1267             |(?:\d+[\p{P}\s]?){1,4}    # or by same block as digits block
1268             )
1269             \s?\p{L}{0,4}\s?           # followed or not by a whitespace. \p{L}{0,4}\s? are for cases like '$9.50 USD'
1270             )                          # end of capturing parenthesis
1271             (?:\p{P}|\z)               # followed by a punctuation sign or by the end of the string
1272             /gx);
1273
1274         if ( @matches ) {
1275             foreach ( @matches ) {
1276                 $localprice = $_ and last if index($_, $isocode)>=0;
1277             }
1278             if ( !$localprice ) {
1279                 foreach ( @matches ) {
1280                     $localprice = $_ and last if $_=~ /(^|[^\p{Sc}\p{L}\/])\Q$symbol\E([^\p{Sc}\p{L}\/]+\z|\z)/;
1281                 }
1282             }
1283         }
1284     }
1285     if ( $localprice ) {
1286         $price = $localprice;
1287     } else {
1288         ## Grab the first number in the string ( can use commas or periods for thousands separator and/or decimal separator )
1289         ( $price ) = $price =~ m/([\d\,\.]+[[\,\.]\d\d]?)/;
1290     }
1291     # eliminate symbol/isocode, space and any final dot from the string
1292     $price =~ s/[\p{Sc}\p{L}\/ ]|\.$//g;
1293     # remove comma,dot when used as separators from hundreds
1294     $price =~s/[\,\.](\d{3})/$1/g;
1295     # convert comma to dot to ensure correct display of decimals if existing
1296     $price =~s/,/./;
1297     return $price;
1298 }
1299
1300
1301 =head2 GetMarcQuantity
1302
1303 return the quantity of a book. Used in acquisition only, when importing a file an iso2709 from a bookseller
1304 Warning : this is not really in the marc standard. In Unimarc, Electre (the most widely used bookseller) use the 969$a
1305
1306 returns 0 if no quantity found
1307 returns undef if called without a marc record or with
1308 an unrecognized marc format
1309
1310 =cut
1311
1312 sub GetMarcQuantity {
1313     my ( $record, $marcflavour ) = @_;
1314     if (!$record) {
1315         carp 'GetMarcQuantity called on undefined record';
1316         return;
1317     }
1318
1319     my @listtags;
1320     my $subfield;
1321     
1322     if ( $marcflavour eq "MARC21" ) {
1323         return 0
1324     } elsif ( $marcflavour eq "UNIMARC" ) {
1325         @listtags = ('969');
1326         $subfield="a";
1327     } else {
1328         return;
1329     }
1330     
1331     for my $field ( $record->field(@listtags) ) {
1332         for my $subfield_value  ($field->subfield($subfield)){
1333             #check value
1334             if ($subfield_value) {
1335                  # in France, the cents separator is the , but sometimes, ppl use a .
1336                  # in this case, the price will be x100 when unformatted ! Replace the . by a , to get a proper price calculation
1337                 $subfield_value =~ s/\./,/ if C4::Context->preference("CurrencyFormat") eq "FR";
1338                 return $subfield_value;
1339             }
1340         }
1341     }
1342     return 0; # no price found
1343 }
1344
1345
1346 =head2 GetAuthorisedValueDesc
1347
1348   my $subfieldvalue =get_authorised_value_desc(
1349     $tag, $subf[$i][0],$subf[$i][1], '', $taglib, $category, $opac);
1350
1351 Retrieve the complete description for a given authorised value.
1352
1353 Now takes $category and $value pair too.
1354
1355   my $auth_value_desc =GetAuthorisedValueDesc(
1356     '','', 'DVD' ,'','','CCODE');
1357
1358 If the optional $opac parameter is set to a true value, displays OPAC 
1359 descriptions rather than normal ones when they exist.
1360
1361 =cut
1362
1363 sub GetAuthorisedValueDesc {
1364     my ( $tag, $subfield, $value, $framework, $tagslib, $category, $opac ) = @_;
1365
1366     if ( !$category ) {
1367
1368         return $value unless defined $tagslib->{$tag}->{$subfield}->{'authorised_value'};
1369
1370         #---- branch
1371         if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1372             my $branch = Koha::Libraries->find($value);
1373             return $branch? $branch->branchname: q{};
1374         }
1375
1376         #---- itemtypes
1377         if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
1378             my $itemtype = Koha::ItemTypes->find( $value );
1379             return $itemtype ? $itemtype->translated_description : q||;
1380         }
1381
1382         #---- "true" authorized value
1383         $category = $tagslib->{$tag}->{$subfield}->{'authorised_value'};
1384     }
1385
1386     my $dbh = C4::Context->dbh;
1387     if ( $category ne "" ) {
1388         my $sth = $dbh->prepare( "SELECT lib, lib_opac FROM authorised_values WHERE category = ? AND authorised_value = ?" );
1389         $sth->execute( $category, $value );
1390         my $data = $sth->fetchrow_hashref;
1391         return ( $opac && $data->{'lib_opac'} ) ? $data->{'lib_opac'} : $data->{'lib'};
1392     } else {
1393         return $value;    # if nothing is found return the original value
1394     }
1395 }
1396
1397 =head2 GetMarcControlnumber
1398
1399   $marccontrolnumber = GetMarcControlnumber($record,$marcflavour);
1400
1401 Get the control number / record Identifier from the MARC record and return it.
1402
1403 =cut
1404
1405 sub GetMarcControlnumber {
1406     my ( $record, $marcflavour ) = @_;
1407     if (!$record) {
1408         carp 'GetMarcControlnumber called on undefined record';
1409         return;
1410     }
1411     my $controlnumber = "";
1412     # Control number or Record identifier are the same field in MARC21, UNIMARC and NORMARC
1413     # Keep $marcflavour for possible later use
1414     if ($marcflavour eq "MARC21" || $marcflavour eq "UNIMARC" || $marcflavour eq "NORMARC") {
1415         my $controlnumberField = $record->field('001');
1416         if ($controlnumberField) {
1417             $controlnumber = $controlnumberField->data();
1418         }
1419     }
1420     return $controlnumber;
1421 }
1422
1423 =head2 GetMarcISBN
1424
1425   $marcisbnsarray = GetMarcISBN( $record, $marcflavour );
1426
1427 Get all ISBNs from the MARC record and returns them in an array.
1428 ISBNs stored in different fields depending on MARC flavour
1429
1430 =cut
1431
1432 sub GetMarcISBN {
1433     my ( $record, $marcflavour ) = @_;
1434     if (!$record) {
1435         carp 'GetMarcISBN called on undefined record';
1436         return;
1437     }
1438     my $scope;
1439     if ( $marcflavour eq "UNIMARC" ) {
1440         $scope = '010';
1441     } else {    # assume marc21 if not unimarc
1442         $scope = '020';
1443     }
1444
1445     my @marcisbns;
1446     foreach my $field ( $record->field($scope) ) {
1447         my $isbn = $field->subfield( 'a' );
1448         if ( $isbn && $isbn ne "" ) {
1449             push @marcisbns, $isbn;
1450         }
1451     }
1452
1453     return \@marcisbns;
1454 }    # end GetMarcISBN
1455
1456
1457 =head2 GetMarcISSN
1458
1459   $marcissnsarray = GetMarcISSN( $record, $marcflavour );
1460
1461 Get all valid ISSNs from the MARC record and returns them in an array.
1462 ISSNs are stored in different fields depending on MARC flavour
1463
1464 =cut
1465
1466 sub GetMarcISSN {
1467     my ( $record, $marcflavour ) = @_;
1468     if (!$record) {
1469         carp 'GetMarcISSN called on undefined record';
1470         return;
1471     }
1472     my $scope;
1473     if ( $marcflavour eq "UNIMARC" ) {
1474         $scope = '011';
1475     }
1476     else {    # assume MARC21 or NORMARC
1477         $scope = '022';
1478     }
1479     my @marcissns;
1480     foreach my $field ( $record->field($scope) ) {
1481         push @marcissns, $field->subfield( 'a' )
1482             if ( $field->subfield( 'a' ) ne "" );
1483     }
1484     return \@marcissns;
1485 }    # end GetMarcISSN
1486
1487 =head2 GetMarcNotes
1488
1489     $marcnotesarray = GetMarcNotes( $record, $marcflavour );
1490
1491     Get all notes from the MARC record and returns them in an array.
1492     The notes are stored in different fields depending on MARC flavour.
1493     MARC21 5XX $u subfields receive special attention as they are URIs.
1494
1495 =cut
1496
1497 sub GetMarcNotes {
1498     my ( $record, $marcflavour, $opac ) = @_;
1499     if (!$record) {
1500         carp 'GetMarcNotes called on undefined record';
1501         return;
1502     }
1503
1504     my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
1505     my @marcnotes;
1506
1507     #MARC21 specs indicate some notes should be private if first indicator 0
1508     my %maybe_private = (
1509         541 => 1,
1510         542 => 1,
1511         561 => 1,
1512         583 => 1,
1513         590 => 1
1514     );
1515
1516     my %hiddenlist = map { $_ => 1 }
1517         split( /,/, C4::Context->preference('NotesToHide'));
1518     foreach my $field ( $record->field($scope) ) {
1519         my $tag = $field->tag();
1520         next if $hiddenlist{ $tag };
1521         next if $opac && $maybe_private{$tag} && !$field->indicator(1);
1522         if( $marcflavour ne 'UNIMARC' && $field->subfield('u') ) {
1523             # Field 5XX$u always contains URI
1524             # Examples: 505u, 506u, 510u, 514u, 520u, 530u, 538u, 540u, 542u, 552u, 555u, 561u, 563u, 583u
1525             # We first push the other subfields, then all $u's separately
1526             # Leave further actions to the template (see e.g. opac-detail)
1527             my $othersub =
1528                 join '', ( 'a' .. 't', 'v' .. 'z', '0' .. '9' ); # excl 'u'
1529             push @marcnotes, { marcnote => $field->as_string($othersub) };
1530             foreach my $sub ( $field->subfield('u') ) {
1531                 $sub =~ s/^\s+|\s+$//g; # trim
1532                 push @marcnotes, { marcnote => $sub };
1533             }
1534         } else {
1535             push @marcnotes, { marcnote => $field->as_string() };
1536         }
1537     }
1538     return \@marcnotes;
1539 }
1540
1541 =head2 GetMarcSubjects
1542
1543   $marcsubjcts = GetMarcSubjects($record,$marcflavour);
1544
1545 Get all subjects from the MARC record and returns them in an array.
1546 The subjects are stored in different fields depending on MARC flavour
1547
1548 =cut
1549
1550 sub GetMarcSubjects {
1551     my ( $record, $marcflavour ) = @_;
1552     if (!$record) {
1553         carp 'GetMarcSubjects called on undefined record';
1554         return;
1555     }
1556     my ( $mintag, $maxtag, $fields_filter );
1557     if ( $marcflavour eq "UNIMARC" ) {
1558         $mintag = "600";
1559         $maxtag = "611";
1560         $fields_filter = '6..';
1561     } else { # marc21/normarc
1562         $mintag = "600";
1563         $maxtag = "699";
1564         $fields_filter = '6..';
1565     }
1566
1567     my @marcsubjects;
1568
1569     my $subject_limit = C4::Context->preference("TraceCompleteSubfields") ? 'su,complete-subfield' : 'su';
1570     my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
1571
1572     foreach my $field ( $record->field($fields_filter) ) {
1573         next unless ($field->tag() >= $mintag && $field->tag() <= $maxtag);
1574         my @subfields_loop;
1575         my @subfields = $field->subfields();
1576         my @link_loop;
1577
1578         # if there is an authority link, build the links with an= subfield9
1579         my $subfield9 = $field->subfield('9');
1580         my $authoritylink;
1581         if ($subfield9) {
1582             my $linkvalue = $subfield9;
1583             $linkvalue =~ s/(\(|\))//g;
1584             @link_loop = ( { limit => 'an', 'link' => $linkvalue } );
1585             $authoritylink = $linkvalue
1586         }
1587
1588         # other subfields
1589         for my $subject_subfield (@subfields) {
1590             next if ( $subject_subfield->[0] eq '9' );
1591
1592             # don't load unimarc subfields 3,4,5
1593             next if ( ( $marcflavour eq "UNIMARC" ) and ( $subject_subfield->[0] =~ /2|3|4|5/ ) );
1594             # don't load MARC21 subfields 2 (FIXME: any more subfields??)
1595             next if ( ( $marcflavour eq "MARC21" ) and ( $subject_subfield->[0] =~ /2/ ) );
1596
1597             my $code      = $subject_subfield->[0];
1598             my $value     = $subject_subfield->[1];
1599             my $linkvalue = $value;
1600             $linkvalue =~ s/(\(|\))//g;
1601             # if no authority link, build a search query
1602             unless ($subfield9) {
1603                 push @link_loop, {
1604                     limit    => $subject_limit,
1605                     'link'   => $linkvalue,
1606                     operator => (scalar @link_loop) ? ' and ' : undef
1607                 };
1608             }
1609             my @this_link_loop = @link_loop;
1610             # do not display $0
1611             unless ( $code eq '0' ) {
1612                 push @subfields_loop, {
1613                     code      => $code,
1614                     value     => $value,
1615                     link_loop => \@this_link_loop,
1616                     separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
1617                 };
1618             }
1619         }
1620
1621         push @marcsubjects, {
1622             MARCSUBJECT_SUBFIELDS_LOOP => \@subfields_loop,
1623             authoritylink => $authoritylink,
1624         } if $authoritylink || @subfields_loop;
1625
1626     }
1627     return \@marcsubjects;
1628 }    #end getMARCsubjects
1629
1630 =head2 GetMarcAuthors
1631
1632   authors = GetMarcAuthors($record,$marcflavour);
1633
1634 Get all authors from the MARC record and returns them in an array.
1635 The authors are stored in different fields depending on MARC flavour
1636
1637 =cut
1638
1639 sub GetMarcAuthors {
1640     my ( $record, $marcflavour ) = @_;
1641     if (!$record) {
1642         carp 'GetMarcAuthors called on undefined record';
1643         return;
1644     }
1645     my ( $mintag, $maxtag, $fields_filter );
1646
1647     # tagslib useful only for UNIMARC author responsibilities
1648     my $tagslib;
1649     if ( $marcflavour eq "UNIMARC" ) {
1650         # FIXME : we don't have the framework available, we take the default framework. May be buggy on some setups, will be usually correct.
1651         $tagslib = GetMarcStructure( 1, '', { unsafe => 1 });
1652         $mintag = "700";
1653         $maxtag = "712";
1654         $fields_filter = '7..';
1655     } else { # marc21/normarc
1656         $mintag = "700";
1657         $maxtag = "720";
1658         $fields_filter = '7..';
1659     }
1660
1661     my @marcauthors;
1662     my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
1663
1664     foreach my $field ( $record->field($fields_filter) ) {
1665         next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
1666         my @subfields_loop;
1667         my @link_loop;
1668         my @subfields  = $field->subfields();
1669         my $count_auth = 0;
1670
1671         # if there is an authority link, build the link with Koha-Auth-Number: subfield9
1672         my $subfield9 = $field->subfield('9');
1673         if ($subfield9) {
1674             my $linkvalue = $subfield9;
1675             $linkvalue =~ s/(\(|\))//g;
1676             @link_loop = ( { 'limit' => 'an', 'link' => $linkvalue } );
1677         }
1678
1679         # other subfields
1680         my $unimarc3;
1681         for my $authors_subfield (@subfields) {
1682             next if ( $authors_subfield->[0] eq '9' );
1683
1684             # unimarc3 contains the $3 of the author for UNIMARC.
1685             # For french academic libraries, it's the "ppn", and it's required for idref webservice
1686             $unimarc3 = $authors_subfield->[1] if $marcflavour eq 'UNIMARC' and $authors_subfield->[0] =~ /3/;
1687
1688             # don't load unimarc subfields 3, 5
1689             next if ( $marcflavour eq 'UNIMARC' and ( $authors_subfield->[0] =~ /3|5/ ) );
1690
1691             my $code = $authors_subfield->[0];
1692             my $value        = $authors_subfield->[1];
1693             my $linkvalue    = $value;
1694             $linkvalue =~ s/(\(|\))//g;
1695             # UNIMARC author responsibility
1696             if ( $marcflavour eq 'UNIMARC' and $code eq '4' ) {
1697                 $value = GetAuthorisedValueDesc( $field->tag(), $code, $value, '', $tagslib );
1698                 $linkvalue = "($value)";
1699             }
1700             # if no authority link, build a search query
1701             unless ($subfield9) {
1702                 push @link_loop, {
1703                     limit    => 'au',
1704                     'link'   => $linkvalue,
1705                     operator => (scalar @link_loop) ? ' and ' : undef
1706                 };
1707             }
1708             my @this_link_loop = @link_loop;
1709             # do not display $0
1710             unless ( $code eq '0') {
1711                 push @subfields_loop, {
1712                     tag       => $field->tag(),
1713                     code      => $code,
1714                     value     => $value,
1715                     link_loop => \@this_link_loop,
1716                     separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
1717                 };
1718             }
1719         }
1720         push @marcauthors, {
1721             MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop,
1722             authoritylink => $subfield9,
1723             unimarc3 => $unimarc3
1724         };
1725     }
1726     return \@marcauthors;
1727 }
1728
1729 =head2 GetMarcUrls
1730
1731   $marcurls = GetMarcUrls($record,$marcflavour);
1732
1733 Returns arrayref of URLs from MARC data, suitable to pass to tmpl loop.
1734 Assumes web resources (not uncommon in MARC21 to omit resource type ind) 
1735
1736 =cut
1737
1738 sub GetMarcUrls {
1739     my ( $record, $marcflavour ) = @_;
1740     if (!$record) {
1741         carp 'GetMarcUrls called on undefined record';
1742         return;
1743     }
1744
1745     my @marcurls;
1746     for my $field ( $record->field('856') ) {
1747         my @notes;
1748         for my $note ( $field->subfield('z') ) {
1749             push @notes, { note => $note };
1750         }
1751         my @urls = $field->subfield('u');
1752         foreach my $url (@urls) {
1753             $url =~ s/^\s+|\s+$//g; # trim
1754             my $marcurl;
1755             if ( $marcflavour eq 'MARC21' ) {
1756                 my $s3   = $field->subfield('3');
1757                 my $link = $field->subfield('y');
1758                 unless ( $url =~ /^\w+:/ ) {
1759                     if ( $field->indicator(1) eq '7' ) {
1760                         $url = $field->subfield('2') . "://" . $url;
1761                     } elsif ( $field->indicator(1) eq '1' ) {
1762                         $url = 'ftp://' . $url;
1763                     } else {
1764
1765                         #  properly, this should be if ind1=4,
1766                         #  however we will assume http protocol since we're building a link.
1767                         $url = 'http://' . $url;
1768                     }
1769                 }
1770
1771                 # TODO handle ind 2 (relationship)
1772                 $marcurl = {
1773                     MARCURL => $url,
1774                     notes   => \@notes,
1775                 };
1776                 $marcurl->{'linktext'} = $link || $s3 || C4::Context->preference('URLLinkText') || $url;
1777                 $marcurl->{'part'} = $s3 if ($link);
1778                 $marcurl->{'toc'} = 1 if ( defined($s3) && $s3 =~ /^[Tt]able/ );
1779             } else {
1780                 $marcurl->{'linktext'} = $field->subfield('2') || C4::Context->preference('URLLinkText') || $url;
1781                 $marcurl->{'MARCURL'} = $url;
1782             }
1783             push @marcurls, $marcurl;
1784         }
1785     }
1786     return \@marcurls;
1787 }
1788
1789 =head2 GetMarcSeries
1790
1791   $marcseriesarray = GetMarcSeries($record,$marcflavour);
1792
1793 Get all series from the MARC record and returns them in an array.
1794 The series are stored in different fields depending on MARC flavour
1795
1796 =cut
1797
1798 sub GetMarcSeries {
1799     my ( $record, $marcflavour ) = @_;
1800     if (!$record) {
1801         carp 'GetMarcSeries called on undefined record';
1802         return;
1803     }
1804
1805     my ( $mintag, $maxtag, $fields_filter );
1806     if ( $marcflavour eq "UNIMARC" ) {
1807         $mintag = "225";
1808         $maxtag = "225";
1809         $fields_filter = '2..';
1810     } else {    # marc21/normarc
1811         $mintag = "440";
1812         $maxtag = "490";
1813         $fields_filter = '4..';
1814     }
1815
1816     my @marcseries;
1817     my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
1818
1819     foreach my $field ( $record->field($fields_filter) ) {
1820         next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
1821         my @subfields_loop;
1822         my @subfields = $field->subfields();
1823         my @link_loop;
1824
1825         for my $series_subfield (@subfields) {
1826
1827             # ignore $9, used for authority link
1828             next if ( $series_subfield->[0] eq '9' );
1829
1830             my $volume_number;
1831             my $code      = $series_subfield->[0];
1832             my $value     = $series_subfield->[1];
1833             my $linkvalue = $value;
1834             $linkvalue =~ s/(\(|\))//g;
1835
1836             # see if this is an instance of a volume
1837             if ( $code eq 'v' ) {
1838                 $volume_number = 1;
1839             }
1840
1841             push @link_loop, {
1842                 'link' => $linkvalue,
1843                 operator => (scalar @link_loop) ? ' and ' : undef
1844             };
1845
1846             if ($volume_number) {
1847                 push @subfields_loop, { volumenum => $value };
1848             } else {
1849                 push @subfields_loop, {
1850                     code      => $code,
1851                     value     => $value,
1852                     link_loop => \@link_loop,
1853                     separator => (scalar @subfields_loop) ? $AuthoritySeparator : '',
1854                     volumenum => $volume_number,
1855                 }
1856             }
1857         }
1858         push @marcseries, { MARCSERIES_SUBFIELDS_LOOP => \@subfields_loop };
1859
1860     }
1861     return \@marcseries;
1862 }    #end getMARCseriess
1863
1864 =head2 GetMarcHosts
1865
1866   $marchostsarray = GetMarcHosts($record,$marcflavour);
1867
1868 Get all host records (773s MARC21, 461 UNIMARC) from the MARC record and returns them in an array.
1869
1870 =cut
1871
1872 sub GetMarcHosts {
1873     my ( $record, $marcflavour ) = @_;
1874     if (!$record) {
1875         carp 'GetMarcHosts called on undefined record';
1876         return;
1877     }
1878
1879     my ( $tag,$title_subf,$bibnumber_subf,$itemnumber_subf);
1880     $marcflavour ||="MARC21";
1881     if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
1882         $tag = "773";
1883         $title_subf = "t";
1884         $bibnumber_subf ="0";
1885         $itemnumber_subf='9';
1886     }
1887     elsif ($marcflavour eq "UNIMARC") {
1888         $tag = "461";
1889         $title_subf = "t";
1890         $bibnumber_subf ="0";
1891         $itemnumber_subf='9';
1892     };
1893
1894     my @marchosts;
1895
1896     foreach my $field ( $record->field($tag)) {
1897
1898         my @fields_loop;
1899
1900         my $hostbiblionumber = $field->subfield("$bibnumber_subf");
1901         my $hosttitle = $field->subfield($title_subf);
1902         my $hostitemnumber=$field->subfield($itemnumber_subf);
1903         push @fields_loop, { hostbiblionumber => $hostbiblionumber, hosttitle => $hosttitle, hostitemnumber => $hostitemnumber};
1904         push @marchosts, { MARCHOSTS_FIELDS_LOOP => \@fields_loop };
1905
1906         }
1907     my $marchostsarray = \@marchosts;
1908     return $marchostsarray;
1909 }
1910
1911 =head2 UpsertMarcSubfield
1912
1913     my $record = C4::Biblio::UpsertMarcSubfield($MARC::Record, $fieldTag, $subfieldCode, $subfieldContent);
1914
1915 =cut
1916
1917 sub UpsertMarcSubfield {
1918     my ($record, $tag, $code, $content) = @_;
1919     my $f = $record->field($tag);
1920
1921     if ($f) {
1922         $f->update( $code => $content );
1923     }
1924     else {
1925         my $f = MARC::Field->new( $tag, '', '', $code => $content);
1926         $record->insert_fields_ordered( $f );
1927     }
1928 }
1929
1930 =head2 UpsertMarcControlField
1931
1932     my $record = C4::Biblio::UpsertMarcControlField($MARC::Record, $fieldTag, $content);
1933
1934 =cut
1935
1936 sub UpsertMarcControlField {
1937     my ($record, $tag, $content) = @_;
1938     die "UpsertMarcControlField() \$tag '$tag' is not a control field\n" unless 0+$tag < 10;
1939     my $f = $record->field($tag);
1940
1941     if ($f) {
1942         $f->update( $content );
1943     }
1944     else {
1945         my $f = MARC::Field->new($tag, $content);
1946         $record->insert_fields_ordered( $f );
1947     }
1948 }
1949
1950 =head2 GetFrameworkCode
1951
1952   $frameworkcode = GetFrameworkCode( $biblionumber )
1953
1954 =cut
1955
1956 sub GetFrameworkCode {
1957     my ($biblionumber) = @_;
1958     my $dbh            = C4::Context->dbh;
1959     my $sth            = $dbh->prepare("SELECT frameworkcode FROM biblio WHERE biblionumber=?");
1960     $sth->execute($biblionumber);
1961     my ($frameworkcode) = $sth->fetchrow;
1962     return $frameworkcode;
1963 }
1964
1965 =head2 TransformKohaToMarc
1966
1967     $record = TransformKohaToMarc( $hash [, $params ]  )
1968
1969 This function builds a (partial) MARC::Record from a hash.
1970 Hash entries can be from biblio, biblioitems or items.
1971 The params hash includes the parameter no_split used in C4::Items.
1972
1973 This function is called in acquisition module, to create a basic catalogue
1974 entry from user entry.
1975
1976 =cut
1977
1978
1979 sub TransformKohaToMarc {
1980     my ( $hash, $params ) = @_;
1981     my $record = MARC::Record->new();
1982     SetMarcUnicodeFlag( $record, C4::Context->preference("marcflavour") );
1983
1984     # In the next call we use the Default framework, since it is considered
1985     # authoritative for Koha to Marc mappings.
1986     my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # do not change framework
1987     my $tag_hr = {};
1988     while ( my ($kohafield, $value) = each %$hash ) {
1989         foreach my $fld ( @{ $mss->{$kohafield} } ) {
1990             my $tagfield    = $fld->{tagfield};
1991             my $tagsubfield = $fld->{tagsubfield};
1992             next if !$tagfield;
1993
1994             # BZ 21800: split value if field is repeatable.
1995             my @values = _check_split($params, $fld, $value)
1996                 ? split(/\s?\|\s?/, $value, -1)
1997                 : ( $value );
1998             foreach my $value ( @values ) {
1999                 next if $value eq '';
2000                 $tag_hr->{$tagfield} //= [];
2001                 push @{$tag_hr->{$tagfield}}, [($tagsubfield, $value)];
2002             }
2003         }
2004     }
2005     foreach my $tag (sort keys %$tag_hr) {
2006         my @sfl = @{$tag_hr->{$tag}};
2007         @sfl = sort { $a->[0] cmp $b->[0]; } @sfl;
2008         @sfl = map { @{$_}; } @sfl;
2009         # Special care for control fields: remove the subfield indication @
2010         # and do not insert indicators.
2011         my @ind = $tag < 10 ? () : ( " ", " " );
2012         @sfl = grep { $_ ne '@' } @sfl if $tag < 10;
2013         $record->insert_fields_ordered( MARC::Field->new($tag, @ind, @sfl) );
2014     }
2015     return $record;
2016 }
2017
2018 sub _check_split {
2019 # Checks if $value must be split; may consult passed framework
2020     my ($params, $fld, $value) = @_;
2021     return if index($value,'|') == -1; # nothing to worry about
2022     return if $params->{no_split};
2023
2024     # if we did not get a specific framework, check default in $mss
2025     return $fld->{repeatable} if !$params->{framework};
2026
2027     # here we need to check the specific framework
2028     my $mss = GetMarcSubfieldStructure($params->{framework}, { unsafe => 1 });
2029     foreach my $fld2 ( @{ $mss->{ $fld->{kohafield} } } ) {
2030         next if $fld2->{tagfield} ne $fld->{tagfield};
2031         next if $fld2->{tagsubfield} ne $fld->{tagsubfield};
2032         return 1 if $fld2->{repeatable};
2033     }
2034     return;
2035 }
2036
2037 =head2 PrepHostMarcField
2038
2039     $hostfield = PrepHostMarcField ( $hostbiblionumber,$hostitemnumber,$marcflavour )
2040
2041 This function returns a host field populated with data from the host record, the field can then be added to an analytical record
2042
2043 =cut
2044
2045 sub PrepHostMarcField {
2046     my ($hostbiblionumber,$hostitemnumber, $marcflavour) = @_;
2047     $marcflavour ||="MARC21";
2048     
2049     my $hostrecord = GetMarcBiblio({ biblionumber => $hostbiblionumber });
2050     my $item = Koha::Items->find($hostitemnumber);
2051
2052         my $hostmarcfield;
2053     if ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) {
2054         
2055         #main entry
2056         my $mainentry;
2057         if ($hostrecord->subfield('100','a')){
2058             $mainentry = $hostrecord->subfield('100','a');
2059         } elsif ($hostrecord->subfield('110','a')){
2060             $mainentry = $hostrecord->subfield('110','a');
2061         } else {
2062             $mainentry = $hostrecord->subfield('111','a');
2063         }
2064         
2065         # qualification info
2066         my $qualinfo;
2067         if (my $field260 = $hostrecord->field('260')){
2068             $qualinfo =  $field260->as_string( 'abc' );
2069         }
2070         
2071
2072         #other fields
2073         my $ed = $hostrecord->subfield('250','a');
2074         my $barcode = $item->barcode;
2075         my $title = $hostrecord->subfield('245','a');
2076
2077         # record control number, 001 with 003 and prefix
2078         my $recctrlno;
2079         if ($hostrecord->field('001')){
2080             $recctrlno = $hostrecord->field('001')->data();
2081             if ($hostrecord->field('003')){
2082                 $recctrlno = '('.$hostrecord->field('003')->data().')'.$recctrlno;
2083             }
2084         }
2085
2086         # issn/isbn
2087         my $issn = $hostrecord->subfield('022','a');
2088         my $isbn = $hostrecord->subfield('020','a');
2089
2090
2091         $hostmarcfield = MARC::Field->new(
2092                 773, '0', '',
2093                 '0' => $hostbiblionumber,
2094                 '9' => $hostitemnumber,
2095                 'a' => $mainentry,
2096                 'b' => $ed,
2097                 'd' => $qualinfo,
2098                 'o' => $barcode,
2099                 't' => $title,
2100                 'w' => $recctrlno,
2101                 'x' => $issn,
2102                 'z' => $isbn
2103                 );
2104     } elsif ($marcflavour eq "UNIMARC") {
2105         $hostmarcfield = MARC::Field->new(
2106             461, '', '',
2107             '0' => $hostbiblionumber,
2108             't' => $hostrecord->subfield('200','a'), 
2109             '9' => $hostitemnumber
2110         );      
2111     };
2112
2113     return $hostmarcfield;
2114 }
2115
2116 =head2 TransformHtmlToXml
2117
2118   $xml = TransformHtmlToXml( $tags, $subfields, $values, $indicator, 
2119                              $ind_tag, $auth_type )
2120
2121 $auth_type contains :
2122
2123 =over
2124
2125 =item - nothing : rebuild a biblio. In UNIMARC the encoding is in 100$a pos 26/27
2126
2127 =item - UNIMARCAUTH : rebuild an authority. In UNIMARC, the encoding is in 100$a pos 13/14
2128
2129 =item - ITEM : rebuild an item : in UNIMARC, 100$a, it's in the biblio ! (otherwise, we would get 2 100 fields !)
2130
2131 =back
2132
2133 =cut
2134
2135 sub TransformHtmlToXml {
2136     my ( $tags, $subfields, $values, $indicator, $ind_tag, $auth_type ) = @_;
2137     # NOTE: The parameter $ind_tag is NOT USED -- BZ 11247
2138
2139     my $xml = MARC::File::XML::header('UTF-8');
2140     $xml .= "<record>\n";
2141     $auth_type = C4::Context->preference('marcflavour') unless $auth_type;
2142     MARC::File::XML->default_record_format($auth_type);
2143
2144     # in UNIMARC, field 100 contains the encoding
2145     # check that there is one, otherwise the
2146     # MARC::Record->new_from_xml will fail (and Koha will die)
2147     my $unimarc_and_100_exist = 0;
2148     $unimarc_and_100_exist = 1 if $auth_type eq 'ITEM';    # if we rebuild an item, no need of a 100 field
2149     my $prevtag = -1;
2150     my $first   = 1;
2151     my $j       = -1;
2152     my $close_last_tag;
2153     for ( my $i = 0 ; $i < @$tags ; $i++ ) {
2154
2155         if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and @$tags[$i] eq "100" and @$subfields[$i] eq "a" ) {
2156
2157             # if we have a 100 field and it's values are not correct, skip them.
2158             # if we don't have any valid 100 field, we will create a default one at the end
2159             my $enc = substr( @$values[$i], 26, 2 );
2160             if ( $enc eq '01' or $enc eq '50' or $enc eq '03' ) {
2161                 $unimarc_and_100_exist = 1;
2162             } else {
2163                 next;
2164             }
2165         }
2166         @$values[$i] =~ s/&/&amp;/g;
2167         @$values[$i] =~ s/</&lt;/g;
2168         @$values[$i] =~ s/>/&gt;/g;
2169         @$values[$i] =~ s/"/&quot;/g;
2170         @$values[$i] =~ s/'/&apos;/g;
2171
2172         if ( ( @$tags[$i] ne $prevtag ) ) {
2173             $close_last_tag = 0;
2174             $j++ unless ( @$tags[$i] eq "" );
2175             my $str = ( $indicator->[$j] // q{} ) . '  '; # extra space prevents substr outside of string warn
2176             my $ind1 = _default_ind_to_space( substr( $str, 0, 1 ) );
2177             my $ind2 = _default_ind_to_space( substr( $str, 1, 1 ) );
2178             if ( !$first ) {
2179                 $xml .= "</datafield>\n";
2180                 if (   ( @$tags[$i] && @$tags[$i] > 10 )
2181                     && ( @$values[$i] ne "" ) ) {
2182                     $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
2183                     $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
2184                     $first = 0;
2185                     $close_last_tag = 1;
2186                 } else {
2187                     $first = 1;
2188                 }
2189             } else {
2190                 if ( @$values[$i] ne "" ) {
2191
2192                     # leader
2193                     if ( @$tags[$i] eq "000" ) {
2194                         $xml .= "<leader>@$values[$i]</leader>\n";
2195                         $first = 1;
2196
2197                         # rest of the fixed fields
2198                     } elsif ( @$tags[$i] < 10 ) {
2199                         $xml .= "<controlfield tag=\"@$tags[$i]\">@$values[$i]</controlfield>\n";
2200                         $first = 1;
2201                     } else {
2202                         $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
2203                         $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
2204                         $first = 0;
2205                         $close_last_tag = 1;
2206                     }
2207                 }
2208             }
2209         } else {    # @$tags[$i] eq $prevtag
2210             if ( @$values[$i] eq "" ) {
2211             } else {
2212                 if ($first) {
2213                     my $str = ( $indicator->[$j] // q{} ) . '  '; # extra space prevents substr outside of string warn
2214                     my $ind1 = _default_ind_to_space( substr( $str, 0, 1 ) );
2215                     my $ind2 = _default_ind_to_space( substr( $str, 1, 1 ) );
2216                     $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
2217                     $first = 0;
2218                     $close_last_tag = 1;
2219                 }
2220                 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
2221             }
2222         }
2223         $prevtag = @$tags[$i];
2224     }
2225     $xml .= "</datafield>\n" if $close_last_tag;
2226     if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and !$unimarc_and_100_exist ) {
2227
2228         #     warn "SETTING 100 for $auth_type";
2229         my $string = strftime( "%Y%m%d", localtime(time) );
2230
2231         # set 50 to position 26 is biblios, 13 if authorities
2232         my $pos = 26;
2233         $pos = 13 if $auth_type eq 'UNIMARCAUTH';
2234         $string = sprintf( "%-*s", 35, $string );
2235         substr( $string, $pos, 6, "50" );
2236         $xml .= "<datafield tag=\"100\" ind1=\"\" ind2=\"\">\n";
2237         $xml .= "<subfield code=\"a\">$string</subfield>\n";
2238         $xml .= "</datafield>\n";
2239     }
2240     $xml .= "</record>\n";
2241     $xml .= MARC::File::XML::footer();
2242     return $xml;
2243 }
2244
2245 =head2 _default_ind_to_space
2246
2247 Passed what should be an indicator returns a space
2248 if its undefined or zero length
2249
2250 =cut
2251
2252 sub _default_ind_to_space {
2253     my $s = shift;
2254     if ( !defined $s || $s eq q{} ) {
2255         return ' ';
2256     }
2257     return $s;
2258 }
2259
2260 =head2 TransformHtmlToMarc
2261
2262     L<$record> = TransformHtmlToMarc(L<$cgi>)
2263     L<$cgi> is the CGI object which contains the values for subfields
2264     {
2265         'tag_010_indicator1_531951' ,
2266         'tag_010_indicator2_531951' ,
2267         'tag_010_code_a_531951_145735' ,
2268         'tag_010_subfield_a_531951_145735' ,
2269         'tag_200_indicator1_873510' ,
2270         'tag_200_indicator2_873510' ,
2271         'tag_200_code_a_873510_673465' ,
2272         'tag_200_subfield_a_873510_673465' ,
2273         'tag_200_code_b_873510_704318' ,
2274         'tag_200_subfield_b_873510_704318' ,
2275         'tag_200_code_e_873510_280822' ,
2276         'tag_200_subfield_e_873510_280822' ,
2277         'tag_200_code_f_873510_110730' ,
2278         'tag_200_subfield_f_873510_110730' ,
2279     }
2280     L<$record> is the MARC::Record object.
2281
2282 =cut
2283
2284 sub TransformHtmlToMarc {
2285     my ($cgi, $isbiblio) = @_;
2286
2287     my @params = $cgi->multi_param();
2288
2289     # explicitly turn on the UTF-8 flag for all
2290     # 'tag_' parameters to avoid incorrect character
2291     # conversion later on
2292     my $cgi_params = $cgi->Vars;
2293     foreach my $param_name ( keys %$cgi_params ) {
2294         if ( $param_name =~ /^tag_/ ) {
2295             my $param_value = $cgi_params->{$param_name};
2296             unless ( Encode::is_utf8( $param_value ) ) {
2297                 $cgi_params->{$param_name} = Encode::decode('UTF-8', $param_value );
2298             }
2299         }
2300     }
2301
2302     # creating a new record
2303     my $record = MARC::Record->new();
2304     my @fields;
2305     my ($biblionumbertagfield, $biblionumbertagsubfield) = (-1, -1);
2306     ($biblionumbertagfield, $biblionumbertagsubfield) =
2307         &GetMarcFromKohaField( "biblio.biblionumber", '' ) if $isbiblio;
2308 #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!
2309     for (my $i = 0; $params[$i]; $i++ ) {    # browse all CGI params
2310         my $param    = $params[$i];
2311         my $newfield = 0;
2312
2313         # if we are on biblionumber, store it in the MARC::Record (it may not be in the edited fields)
2314         if ( $param eq 'biblionumber' ) {
2315             if ( $biblionumbertagfield < 10 ) {
2316                 $newfield = MARC::Field->new( $biblionumbertagfield, scalar $cgi->param($param), );
2317             } else {
2318                 $newfield = MARC::Field->new( $biblionumbertagfield, '', '', "$biblionumbertagsubfield" => scalar $cgi->param($param), );
2319             }
2320             push @fields, $newfield if ($newfield);
2321         } elsif ( $param =~ /^tag_(\d*)_indicator1_/ ) {    # new field start when having 'input name="..._indicator1_..."
2322             my $tag = $1;
2323
2324             my $ind1 = _default_ind_to_space( substr( $cgi->param($param), 0, 1 ) );
2325             my $ind2 = _default_ind_to_space( substr( $cgi->param( $params[ $i + 1 ] ), 0, 1 ) );
2326             $newfield = 0;
2327             my $j = $i + 2;
2328
2329             if ( $tag < 10 ) {                              # no code for theses fields
2330                                                             # in MARC editor, 000 contains the leader.
2331                 next if $tag == $biblionumbertagfield;
2332                 my $fval= $cgi->param($params[$j+1]);
2333                 if ( $tag eq '000' ) {
2334                     # Force a fake leader even if not provided to avoid crashing
2335                     # during decoding MARC record containing UTF-8 characters
2336                     $record->leader(
2337                         length( $fval ) == 24
2338                         ? $fval
2339                         : '     nam a22        4500'
2340                         )
2341                     ;
2342                     # between 001 and 009 (included)
2343                 } elsif ( $fval ne '' ) {
2344                     $newfield = MARC::Field->new( $tag, $fval, );
2345                 }
2346
2347                 # > 009, deal with subfields
2348             } else {
2349                 # browse subfields for this tag (reason for _code_ match)
2350                 while(defined $params[$j] && $params[$j] =~ /_code_/) {
2351                     last unless defined $params[$j+1];
2352                     $j += 2 and next
2353                         if $tag == $biblionumbertagfield and
2354                            $cgi->param($params[$j]) eq $biblionumbertagsubfield;
2355                     #if next param ne subfield, then it was probably empty
2356                     #try next param by incrementing j
2357                     if($params[$j+1]!~/_subfield_/) {$j++; next; }
2358                     my $fkey= $cgi->param($params[$j]);
2359                     my $fval= $cgi->param($params[$j+1]);
2360                     #check if subfield value not empty and field exists
2361                     if($fval ne '' && $newfield) {
2362                         $newfield->add_subfields( $fkey => $fval);
2363                     }
2364                     elsif($fval ne '') {
2365                         $newfield = MARC::Field->new( $tag, $ind1, $ind2, $fkey => $fval );
2366                     }
2367                     $j += 2;
2368                 } #end-of-while
2369                 $i= $j-1; #update i for outer loop accordingly
2370             }
2371             push @fields, $newfield if ($newfield);
2372         }
2373     }
2374
2375     @fields = sort { $a->tag() cmp $b->tag() } @fields;
2376     $record->append_fields(@fields);
2377     return $record;
2378 }
2379
2380 =head2 TransformMarcToKoha
2381
2382     $result = TransformMarcToKoha( $record, undef, $limit )
2383
2384 Extract data from a MARC bib record into a hashref representing
2385 Koha biblio, biblioitems, and items fields.
2386
2387 If passed an undefined record will log the error and return an empty
2388 hash_ref.
2389
2390 =cut
2391
2392 sub TransformMarcToKoha {
2393     my ( $record, $frameworkcode, $limit_table ) = @_;
2394     # FIXME  Parameter $frameworkcode is obsolete and will be removed
2395     $limit_table //= q{};
2396
2397     my $result = {};
2398     if (!defined $record) {
2399         carp('TransformMarcToKoha called with undefined record');
2400         return $result;
2401     }
2402
2403     my %tables = ( biblio => 1, biblioitems => 1, items => 1 );
2404     if( $limit_table eq 'items' ) {
2405         %tables = ( items => 1 );
2406     }
2407
2408     # The next call acknowledges Default as the authoritative framework
2409     # for Koha to MARC mappings.
2410     my $mss = GetMarcSubfieldStructure( '', { unsafe => 1 } ); # Do not change framework
2411     foreach my $kohafield ( keys %{ $mss } ) {
2412         my ( $table, $column ) = split /[.]/, $kohafield, 2;
2413         next unless $tables{$table};
2414         my $val = TransformMarcToKohaOneField( $kohafield, $record );
2415         next if !defined $val;
2416         my $key = _disambiguate( $table, $column );
2417         $result->{$key} = $val;
2418     }
2419     return $result;
2420 }
2421
2422 =head2 _disambiguate
2423
2424   $newkey = _disambiguate($table, $field);
2425
2426 This is a temporary hack to distinguish between the
2427 following sets of columns when using TransformMarcToKoha.
2428
2429   items.cn_source & biblioitems.cn_source
2430   items.cn_sort & biblioitems.cn_sort
2431
2432 Columns that are currently NOT distinguished (FIXME
2433 due to lack of time to fully test) are:
2434
2435   biblio.notes and biblioitems.notes
2436   biblionumber
2437   timestamp
2438   biblioitemnumber
2439
2440 FIXME - this is necessary because prefixing each column
2441 name with the table name would require changing lots
2442 of code and templates, and exposing more of the DB
2443 structure than is good to the UI templates, particularly
2444 since biblio and bibloitems may well merge in a future
2445 version.  In the future, it would also be good to 
2446 separate DB access and UI presentation field names
2447 more.
2448
2449 =cut
2450
2451 sub _disambiguate {
2452     my ( $table, $column ) = @_;
2453     if ( $column eq "cn_sort" or $column eq "cn_source" ) {
2454         return $table . '.' . $column;
2455     } else {
2456         return $column;
2457     }
2458
2459 }
2460
2461 =head2 TransformMarcToKohaOneField
2462
2463     $val = TransformMarcToKohaOneField( 'biblio.title', $marc );
2464
2465     Note: The authoritative Default framework is used implicitly.
2466
2467 =cut
2468
2469 sub TransformMarcToKohaOneField {
2470     my ( $kohafield, $marc ) = @_;
2471
2472     my ( @rv, $retval );
2473     my @mss = GetMarcSubfieldStructureFromKohaField($kohafield);
2474     foreach my $fldhash ( @mss ) {
2475         my $tag = $fldhash->{tagfield};
2476         my $sub = $fldhash->{tagsubfield};
2477         foreach my $fld ( $marc->field($tag) ) {
2478             if( $sub eq '@' || $fld->is_control_field ) {
2479                 push @rv, $fld->data if $fld->data;
2480             } else {
2481                 push @rv, grep { $_ } $fld->subfield($sub);
2482             }
2483         }
2484     }
2485     return unless @rv;
2486     $retval = join ' | ', uniq(@rv);
2487
2488     # Additional polishing for individual kohafields
2489     if( $kohafield =~ /copyrightdate|publicationyear/ ) {
2490         $retval = _adjust_pubyear( $retval );
2491     }
2492
2493     return $retval;
2494 }
2495
2496 =head2 _adjust_pubyear
2497
2498     Helper routine for TransformMarcToKohaOneField
2499
2500 =cut
2501
2502 sub _adjust_pubyear {
2503     my $retval = shift;
2504     # modify return value to keep only the 1st year found
2505     if( $retval =~ m/c(\d\d\d\d)/ and $1 > 0 ) { # search cYYYY first
2506         $retval = $1;
2507     } elsif( $retval =~ m/(\d\d\d\d)/ && $1 > 0 ) {
2508         $retval = $1;
2509     } elsif( $retval =~ m/
2510              (?<year>\d)[-]?[.Xx?]{3}
2511             |(?<year>\d{2})[.Xx?]{2}
2512             |(?<year>\d{3})[.Xx?]
2513             |(?<year>\d)[-]{3}\?
2514             |(?<year>\d\d)[-]{2}\?
2515             |(?<year>\d{3})[-]\?
2516     /xms ) { # the form 198-? occurred in Dutch ISBD rules
2517         my $digits = $+{year};
2518         $retval = $digits * ( 10 ** ( 4 - length($digits) ));
2519     }
2520     return $retval;
2521 }
2522
2523 =head2 CountItemsIssued
2524
2525     my $count = CountItemsIssued( $biblionumber );
2526
2527 =cut
2528
2529 sub CountItemsIssued {
2530     my ($biblionumber) = @_;
2531     my $dbh            = C4::Context->dbh;
2532     my $sth            = $dbh->prepare('SELECT COUNT(*) as issuedCount FROM items, issues WHERE items.itemnumber = issues.itemnumber AND items.biblionumber = ?');
2533     $sth->execute($biblionumber);
2534     my $row = $sth->fetchrow_hashref();
2535     return $row->{'issuedCount'};
2536 }
2537
2538 =head2 ModZebra
2539
2540   ModZebra( $biblionumber, $op, $server, $record );
2541
2542 $biblionumber is the biblionumber we want to index
2543
2544 $op is specialUpdate or recordDelete, and is used to know what we want to do
2545
2546 $server is the server that we want to update
2547
2548 $record is the update MARC record if it's available. If it's not supplied
2549 and is needed, it'll be loaded from the database.
2550
2551 =cut
2552
2553 sub ModZebra {
2554 ###Accepts a $server variable thus we can use it for biblios authorities or other zebra dbs
2555     my ( $biblionumber, $op, $server, $record ) = @_;
2556     $debug && warn "ModZebra: update requested for: $biblionumber $op $server\n";
2557     if ( C4::Context->preference('SearchEngine') eq 'Elasticsearch' ) {
2558
2559         # TODO abstract to a standard API that'll work for whatever
2560         require Koha::SearchEngine::Elasticsearch::Indexer;
2561         my $indexer = Koha::SearchEngine::Elasticsearch::Indexer->new(
2562             {
2563                 index => $server eq 'biblioserver'
2564                 ? $Koha::SearchEngine::BIBLIOS_INDEX
2565                 : $Koha::SearchEngine::AUTHORITIES_INDEX
2566             }
2567         );
2568         if ( $op eq 'specialUpdate' ) {
2569             unless ($record) {
2570                 $record = GetMarcBiblio({
2571                     biblionumber => $biblionumber,
2572                     embed_items  => 1 });
2573             }
2574             my $records = [$record];
2575             $indexer->update_index_background( [$biblionumber], [$record] );
2576         }
2577         elsif ( $op eq 'recordDelete' ) {
2578             $indexer->delete_index_background( [$biblionumber] );
2579         }
2580         else {
2581             croak "ModZebra called with unknown operation: $op";
2582         }
2583     }
2584
2585     my $dbh = C4::Context->dbh;
2586
2587     # true ModZebra commented until indexdata fixes zebraDB crashes (it seems they occur on multiple updates
2588     # at the same time
2589     # replaced by a zebraqueue table, that is filled with ModZebra to run.
2590     # the table is emptied by rebuild_zebra.pl script (using the -z switch)
2591     my $check_sql = "SELECT COUNT(*) FROM zebraqueue
2592     WHERE server = ?
2593         AND   biblio_auth_number = ?
2594         AND   operation = ?
2595         AND   done = 0";
2596     my $check_sth = $dbh->prepare_cached($check_sql);
2597     $check_sth->execute( $server, $biblionumber, $op );
2598     my ($count) = $check_sth->fetchrow_array;
2599     $check_sth->finish();
2600     if ( $count == 0 ) {
2601         my $sth = $dbh->prepare("INSERT INTO zebraqueue  (biblio_auth_number,server,operation) VALUES(?,?,?)");
2602         $sth->execute( $biblionumber, $server, $op );
2603         $sth->finish;
2604     }
2605 }
2606
2607
2608 =head2 EmbedItemsInMarcBiblio
2609
2610     EmbedItemsInMarcBiblio({
2611         marc_record  => $marc,
2612         biblionumber => $biblionumber,
2613         item_numbers => $itemnumbers,
2614         opac         => $opac });
2615
2616 Given a MARC::Record object containing a bib record,
2617 modify it to include the items attached to it as 9XX
2618 per the bib's MARC framework.
2619 if $itemnumbers is defined, only specified itemnumbers are embedded.
2620
2621 If $opac is true, then opac-relevant suppressions are included.
2622
2623 If opac filtering will be done, borcat should be passed to properly
2624 override if necessary.
2625
2626 =cut
2627
2628 sub EmbedItemsInMarcBiblio {
2629     my ($params) = @_;
2630     my ($marc, $biblionumber, $itemnumbers, $opac, $borcat);
2631     $marc = $params->{marc_record};
2632     if ( !$marc ) {
2633         carp 'EmbedItemsInMarcBiblio: No MARC record passed';
2634         return;
2635     }
2636     $biblionumber = $params->{biblionumber};
2637     $itemnumbers = $params->{item_numbers};
2638     $opac = $params->{opac};
2639     $borcat = $params->{borcat} // q{};
2640
2641     $itemnumbers = [] unless defined $itemnumbers;
2642
2643     my $frameworkcode = GetFrameworkCode($biblionumber);
2644     _strip_item_fields($marc, $frameworkcode);
2645
2646     # ... and embed the current items
2647     my $dbh = C4::Context->dbh;
2648     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
2649     $sth->execute($biblionumber);
2650     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber" );
2651
2652     my @item_fields; # Array holding the actual MARC data for items to be included.
2653     my @items;       # Array holding items which are both in the list (sitenumbers)
2654                      # and on this biblionumber
2655
2656     # Flag indicating if there is potential hiding.
2657     my $opachiddenitems = $opac
2658       && ( C4::Context->preference('OpacHiddenItems') !~ /^\s*$/ );
2659
2660     require C4::Items;
2661     while ( my ($itemnumber) = $sth->fetchrow_array ) {
2662         next if @$itemnumbers and not grep { $_ == $itemnumber } @$itemnumbers;
2663         my $item;
2664         if ( $opachiddenitems ) {
2665             $item = Koha::Items->find($itemnumber);
2666             $item = $item ? $item->unblessed : undef;
2667         }
2668         push @items, { itemnumber => $itemnumber, item => $item };
2669     }
2670     my @items2pass = map { $_->{item} } @items;
2671     my @hiddenitems =
2672       $opachiddenitems
2673       ? C4::Items::GetHiddenItemnumbers({
2674             items  => \@items2pass,
2675             borcat => $borcat })
2676       : ();
2677     # Convert to a hash for quick searching
2678     my %hiddenitems = map { $_ => 1 } @hiddenitems;
2679     foreach my $itemnumber ( map { $_->{itemnumber} } @items ) {
2680         next if $hiddenitems{$itemnumber};
2681         my $item_marc = C4::Items::GetMarcItem( $biblionumber, $itemnumber );
2682         push @item_fields, $item_marc->field($itemtag);
2683     }
2684     $marc->append_fields(@item_fields);
2685 }
2686
2687 =head1 INTERNAL FUNCTIONS
2688
2689 =head2 _koha_marc_update_bib_ids
2690
2691
2692   _koha_marc_update_bib_ids($record, $frameworkcode, $biblionumber, $biblioitemnumber);
2693
2694 Internal function to add or update biblionumber and biblioitemnumber to
2695 the MARC XML.
2696
2697 =cut
2698
2699 sub _koha_marc_update_bib_ids {
2700     my ( $record, $frameworkcode, $biblionumber, $biblioitemnumber ) = @_;
2701
2702     my ( $biblio_tag,     $biblio_subfield )     = GetMarcFromKohaField( "biblio.biblionumber" );
2703     die qq{No biblionumber tag for framework "$frameworkcode"} unless $biblio_tag;
2704     my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.biblioitemnumber" );
2705     die qq{No biblioitemnumber tag for framework "$frameworkcode"} unless $biblioitem_tag;
2706
2707     if ( $biblio_tag < 10 ) {
2708         C4::Biblio::UpsertMarcControlField( $record, $biblio_tag, $biblionumber );
2709     } else {
2710         C4::Biblio::UpsertMarcSubfield($record, $biblio_tag, $biblio_subfield, $biblionumber);
2711     }
2712     if ( $biblioitem_tag < 10 ) {
2713         C4::Biblio::UpsertMarcControlField( $record, $biblioitem_tag, $biblioitemnumber );
2714     } else {
2715         C4::Biblio::UpsertMarcSubfield($record, $biblioitem_tag, $biblioitem_subfield, $biblioitemnumber);
2716     }
2717 }
2718
2719 =head2 _koha_marc_update_biblioitem_cn_sort
2720
2721   _koha_marc_update_biblioitem_cn_sort($marc, $biblioitem, $frameworkcode);
2722
2723 Given a MARC bib record and the biblioitem hash, update the
2724 subfield that contains a copy of the value of biblioitems.cn_sort.
2725
2726 =cut
2727
2728 sub _koha_marc_update_biblioitem_cn_sort {
2729     my $marc          = shift;
2730     my $biblioitem    = shift;
2731     my $frameworkcode = shift;
2732
2733     my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.cn_sort" );
2734     return unless $biblioitem_tag;
2735
2736     my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
2737
2738     if ( my $field = $marc->field($biblioitem_tag) ) {
2739         $field->delete_subfield( code => $biblioitem_subfield );
2740         if ( $cn_sort ne '' ) {
2741             $field->add_subfields( $biblioitem_subfield => $cn_sort );
2742         }
2743     } else {
2744
2745         # if we get here, no biblioitem tag is present in the MARC record, so
2746         # we'll create it if $cn_sort is not empty -- this would be
2747         # an odd combination of events, however
2748         if ($cn_sort) {
2749             $marc->insert_grouped_field( MARC::Field->new( $biblioitem_tag, ' ', ' ', $biblioitem_subfield => $cn_sort ) );
2750         }
2751     }
2752 }
2753
2754 =head2 _koha_add_biblio
2755
2756   my ($biblionumber,$error) = _koha_add_biblio($dbh,$biblioitem);
2757
2758 Internal function to add a biblio ($biblio is a hash with the values)
2759
2760 =cut
2761
2762 sub _koha_add_biblio {
2763     my ( $dbh, $biblio, $frameworkcode ) = @_;
2764
2765     my $error;
2766
2767     # set the series flag
2768     unless (defined $biblio->{'serial'}){
2769         $biblio->{'serial'} = 0;
2770         if ( $biblio->{'seriestitle'} ) { $biblio->{'serial'} = 1 }
2771     }
2772
2773     my $query = "INSERT INTO biblio
2774         SET frameworkcode = ?,
2775             author = ?,
2776             title = ?,
2777             subtitle = ?,
2778             medium = ?,
2779             part_number = ?,
2780             part_name = ?,
2781             unititle =?,
2782             notes = ?,
2783             serial = ?,
2784             seriestitle = ?,
2785             copyrightdate = ?,
2786             datecreated=NOW(),
2787             abstract = ?
2788         ";
2789     my $sth = $dbh->prepare($query);
2790     $sth->execute(
2791         $frameworkcode,        $biblio->{'author'},      $biblio->{'title'},       $biblio->{'subtitle'},
2792         $biblio->{'medium'},   $biblio->{'part_number'}, $biblio->{'part_name'},   $biblio->{'unititle'},
2793         $biblio->{'notes'},    $biblio->{'serial'},      $biblio->{'seriestitle'}, $biblio->{'copyrightdate'},
2794         $biblio->{'abstract'}
2795     );
2796
2797     my $biblionumber = $dbh->{'mysql_insertid'};
2798     if ( $dbh->errstr ) {
2799         $error .= "ERROR in _koha_add_biblio $query" . $dbh->errstr;
2800         warn $error;
2801     }
2802
2803     $sth->finish();
2804
2805     #warn "LEAVING _koha_add_biblio: ".$biblionumber."\n";
2806     return ( $biblionumber, $error );
2807 }
2808
2809 =head2 _koha_modify_biblio
2810
2811   my ($biblionumber,$error) == _koha_modify_biblio($dbh,$biblio,$frameworkcode);
2812
2813 Internal function for updating the biblio table
2814
2815 =cut
2816
2817 sub _koha_modify_biblio {
2818     my ( $dbh, $biblio, $frameworkcode ) = @_;
2819     my $error;
2820
2821     my $query = "
2822         UPDATE biblio
2823         SET    frameworkcode = ?,
2824                author = ?,
2825                title = ?,
2826                subtitle = ?,
2827                medium = ?,
2828                part_number = ?,
2829                part_name = ?,
2830                unititle = ?,
2831                notes = ?,
2832                serial = ?,
2833                seriestitle = ?,
2834                copyrightdate = ?,
2835                abstract = ?
2836         WHERE  biblionumber = ?
2837         "
2838       ;
2839     my $sth = $dbh->prepare($query);
2840
2841     $sth->execute(
2842         $frameworkcode,        $biblio->{'author'},      $biblio->{'title'},       $biblio->{'subtitle'},
2843         $biblio->{'medium'},   $biblio->{'part_number'}, $biblio->{'part_name'},   $biblio->{'unititle'},
2844         $biblio->{'notes'},    $biblio->{'serial'},      $biblio->{'seriestitle'}, $biblio->{'copyrightdate'} ? int($biblio->{'copyrightdate'}) : undef,
2845         $biblio->{'abstract'}, $biblio->{'biblionumber'}
2846     ) if $biblio->{'biblionumber'};
2847
2848     if ( $dbh->errstr || !$biblio->{'biblionumber'} ) {
2849         $error .= "ERROR in _koha_modify_biblio $query" . $dbh->errstr;
2850         warn $error;
2851     }
2852     return ( $biblio->{'biblionumber'}, $error );
2853 }
2854
2855 =head2 _koha_modify_biblioitem_nonmarc
2856
2857   my ($biblioitemnumber,$error) = _koha_modify_biblioitem_nonmarc( $dbh, $biblioitem );
2858
2859 =cut
2860
2861 sub _koha_modify_biblioitem_nonmarc {
2862     my ( $dbh, $biblioitem ) = @_;
2863     my $error;
2864
2865     # re-calculate the cn_sort, it may have changed
2866     my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
2867
2868     my $query = "UPDATE biblioitems 
2869     SET biblionumber    = ?,
2870         volume          = ?,
2871         number          = ?,
2872         itemtype        = ?,
2873         isbn            = ?,
2874         issn            = ?,
2875         publicationyear = ?,
2876         publishercode   = ?,
2877         volumedate      = ?,
2878         volumedesc      = ?,
2879         collectiontitle = ?,
2880         collectionissn  = ?,
2881         collectionvolume= ?,
2882         editionstatement= ?,
2883         editionresponsibility = ?,
2884         illus           = ?,
2885         pages           = ?,
2886         notes           = ?,
2887         size            = ?,
2888         place           = ?,
2889         lccn            = ?,
2890         url             = ?,
2891         cn_source       = ?,
2892         cn_class        = ?,
2893         cn_item         = ?,
2894         cn_suffix       = ?,
2895         cn_sort         = ?,
2896         totalissues     = ?,
2897         ean             = ?,
2898         agerestriction  = ?
2899         where biblioitemnumber = ?
2900         ";
2901     my $sth = $dbh->prepare($query);
2902     $sth->execute(
2903         $biblioitem->{'biblionumber'},     $biblioitem->{'volume'},           $biblioitem->{'number'},                $biblioitem->{'itemtype'},
2904         $biblioitem->{'isbn'},             $biblioitem->{'issn'},             $biblioitem->{'publicationyear'},       $biblioitem->{'publishercode'},
2905         $biblioitem->{'volumedate'},       $biblioitem->{'volumedesc'},       $biblioitem->{'collectiontitle'},       $biblioitem->{'collectionissn'},
2906         $biblioitem->{'collectionvolume'}, $biblioitem->{'editionstatement'}, $biblioitem->{'editionresponsibility'}, $biblioitem->{'illus'},
2907         $biblioitem->{'pages'},            $biblioitem->{'bnotes'},           $biblioitem->{'size'},                  $biblioitem->{'place'},
2908         $biblioitem->{'lccn'},             $biblioitem->{'url'},              $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'},
2909         $biblioitem->{'cn_item'},          $biblioitem->{'cn_suffix'},        $cn_sort,                               $biblioitem->{'totalissues'},
2910         $biblioitem->{'ean'},              $biblioitem->{'agerestriction'},   $biblioitem->{'biblioitemnumber'}
2911     );
2912     if ( $dbh->errstr ) {
2913         $error .= "ERROR in _koha_modify_biblioitem_nonmarc $query" . $dbh->errstr;
2914         warn $error;
2915     }
2916     return ( $biblioitem->{'biblioitemnumber'}, $error );
2917 }
2918
2919 =head2 _koha_add_biblioitem
2920
2921   my ($biblioitemnumber,$error) = _koha_add_biblioitem( $dbh, $biblioitem );
2922
2923 Internal function to add a biblioitem
2924
2925 =cut
2926
2927 sub _koha_add_biblioitem {
2928     my ( $dbh, $biblioitem ) = @_;
2929     my $error;
2930
2931     my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
2932     my $query = "INSERT INTO biblioitems SET
2933         biblionumber    = ?,
2934         volume          = ?,
2935         number          = ?,
2936         itemtype        = ?,
2937         isbn            = ?,
2938         issn            = ?,
2939         publicationyear = ?,
2940         publishercode   = ?,
2941         volumedate      = ?,
2942         volumedesc      = ?,
2943         collectiontitle = ?,
2944         collectionissn  = ?,
2945         collectionvolume= ?,
2946         editionstatement= ?,
2947         editionresponsibility = ?,
2948         illus           = ?,
2949         pages           = ?,
2950         notes           = ?,
2951         size            = ?,
2952         place           = ?,
2953         lccn            = ?,
2954         url             = ?,
2955         cn_source       = ?,
2956         cn_class        = ?,
2957         cn_item         = ?,
2958         cn_suffix       = ?,
2959         cn_sort         = ?,
2960         totalissues     = ?,
2961         ean             = ?,
2962         agerestriction  = ?
2963         ";
2964     my $sth = $dbh->prepare($query);
2965     $sth->execute(
2966         $biblioitem->{'biblionumber'},     $biblioitem->{'volume'},           $biblioitem->{'number'},                $biblioitem->{'itemtype'},
2967         $biblioitem->{'isbn'},             $biblioitem->{'issn'},             $biblioitem->{'publicationyear'},       $biblioitem->{'publishercode'},
2968         $biblioitem->{'volumedate'},       $biblioitem->{'volumedesc'},       $biblioitem->{'collectiontitle'},       $biblioitem->{'collectionissn'},
2969         $biblioitem->{'collectionvolume'}, $biblioitem->{'editionstatement'}, $biblioitem->{'editionresponsibility'}, $biblioitem->{'illus'},
2970         $biblioitem->{'pages'},            $biblioitem->{'bnotes'},           $biblioitem->{'size'},                  $biblioitem->{'place'},
2971         $biblioitem->{'lccn'},             $biblioitem->{'url'},                   $biblioitem->{'biblioitems.cn_source'},
2972         $biblioitem->{'cn_class'},         $biblioitem->{'cn_item'},          $biblioitem->{'cn_suffix'},             $cn_sort,
2973         $biblioitem->{'totalissues'},      $biblioitem->{'ean'},              $biblioitem->{'agerestriction'}
2974     );
2975     my $bibitemnum = $dbh->{'mysql_insertid'};
2976
2977     if ( $dbh->errstr ) {
2978         $error .= "ERROR in _koha_add_biblioitem $query" . $dbh->errstr;
2979         warn $error;
2980     }
2981     $sth->finish();
2982     return ( $bibitemnum, $error );
2983 }
2984
2985 =head2 _koha_delete_biblio
2986
2987   $error = _koha_delete_biblio($dbh,$biblionumber);
2988
2989 Internal sub for deleting from biblio table -- also saves to deletedbiblio
2990
2991 C<$dbh> - the database handle
2992
2993 C<$biblionumber> - the biblionumber of the biblio to be deleted
2994
2995 =cut
2996
2997 # FIXME: add error handling
2998
2999 sub _koha_delete_biblio {
3000     my ( $dbh, $biblionumber ) = @_;
3001
3002     # get all the data for this biblio
3003     my $sth = $dbh->prepare("SELECT * FROM biblio WHERE biblionumber=?");
3004     $sth->execute($biblionumber);
3005
3006     # FIXME There is a transaction in _koha_delete_biblio_metadata
3007     # But actually all the following should be done inside a single transaction
3008     if ( my $data = $sth->fetchrow_hashref ) {
3009
3010         # save the record in deletedbiblio
3011         # find the fields to save
3012         my $query = "INSERT INTO deletedbiblio SET ";
3013         my @bind  = ();
3014         foreach my $temp ( keys %$data ) {
3015             $query .= "$temp = ?,";
3016             push( @bind, $data->{$temp} );
3017         }
3018
3019         # replace the last , by ",?)"
3020         $query =~ s/\,$//;
3021         my $bkup_sth = $dbh->prepare($query);
3022         $bkup_sth->execute(@bind);
3023         $bkup_sth->finish;
3024
3025         _koha_delete_biblio_metadata( $biblionumber );
3026
3027         # delete the biblio
3028         my $sth2 = $dbh->prepare("DELETE FROM biblio WHERE biblionumber=?");
3029         $sth2->execute($biblionumber);
3030         # update the timestamp (Bugzilla 7146)
3031         $sth2= $dbh->prepare("UPDATE deletedbiblio SET timestamp=NOW() WHERE biblionumber=?");
3032         $sth2->execute($biblionumber);
3033         $sth2->finish;
3034     }
3035     $sth->finish;
3036     return;
3037 }
3038
3039 =head2 _koha_delete_biblioitems
3040
3041   $error = _koha_delete_biblioitems($dbh,$biblioitemnumber);
3042
3043 Internal sub for deleting from biblioitems table -- also saves to deletedbiblioitems
3044
3045 C<$dbh> - the database handle
3046 C<$biblionumber> - the biblioitemnumber of the biblioitem to be deleted
3047
3048 =cut
3049
3050 # FIXME: add error handling
3051
3052 sub _koha_delete_biblioitems {
3053     my ( $dbh, $biblioitemnumber ) = @_;
3054
3055     # get all the data for this biblioitem
3056     my $sth = $dbh->prepare("SELECT * FROM biblioitems WHERE biblioitemnumber=?");
3057     $sth->execute($biblioitemnumber);
3058
3059     if ( my $data = $sth->fetchrow_hashref ) {
3060
3061         # save the record in deletedbiblioitems
3062         # find the fields to save
3063         my $query = "INSERT INTO deletedbiblioitems SET ";
3064         my @bind  = ();
3065         foreach my $temp ( keys %$data ) {
3066             $query .= "$temp = ?,";
3067             push( @bind, $data->{$temp} );
3068         }
3069
3070         # replace the last , by ",?)"
3071         $query =~ s/\,$//;
3072         my $bkup_sth = $dbh->prepare($query);
3073         $bkup_sth->execute(@bind);
3074         $bkup_sth->finish;
3075
3076         # delete the biblioitem
3077         my $sth2 = $dbh->prepare("DELETE FROM biblioitems WHERE biblioitemnumber=?");
3078         $sth2->execute($biblioitemnumber);
3079         # update the timestamp (Bugzilla 7146)
3080         $sth2= $dbh->prepare("UPDATE deletedbiblioitems SET timestamp=NOW() WHERE biblioitemnumber=?");
3081         $sth2->execute($biblioitemnumber);
3082         $sth2->finish;
3083     }
3084     $sth->finish;
3085     return;
3086 }
3087
3088 =head2 _koha_delete_biblio_metadata
3089
3090   $error = _koha_delete_biblio_metadata($biblionumber);
3091
3092 C<$biblionumber> - the biblionumber of the biblio metadata to be deleted
3093
3094 =cut
3095
3096 sub _koha_delete_biblio_metadata {
3097     my ($biblionumber) = @_;
3098
3099     my $dbh    = C4::Context->dbh;
3100     my $schema = Koha::Database->new->schema;
3101     $schema->txn_do(
3102         sub {
3103             $dbh->do( q|
3104                 INSERT INTO deletedbiblio_metadata (biblionumber, format, `schema`, metadata)
3105                 SELECT biblionumber, format, `schema`, metadata FROM biblio_metadata WHERE biblionumber=?
3106             |,  undef, $biblionumber );
3107             $dbh->do( q|DELETE FROM biblio_metadata WHERE biblionumber=?|,
3108                 undef, $biblionumber );
3109         }
3110     );
3111 }
3112
3113 =head1 UNEXPORTED FUNCTIONS
3114
3115 =head2 ModBiblioMarc
3116
3117   &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3118
3119 Add MARC XML data for a biblio to koha
3120
3121 Function exported, but should NOT be used, unless you really know what you're doing
3122
3123 =cut
3124
3125 sub ModBiblioMarc {
3126     # pass the MARC::Record to this function, and it will create the records in
3127     # the marcxml field
3128     my ( $record, $biblionumber, $frameworkcode ) = @_;
3129     if ( !$record ) {
3130         carp 'ModBiblioMarc passed an undefined record';
3131         return;
3132     }
3133
3134     # Clone record as it gets modified
3135     $record = $record->clone();
3136     my $dbh    = C4::Context->dbh;
3137     my @fields = $record->fields();
3138     if ( !$frameworkcode ) {
3139         $frameworkcode = "";
3140     }
3141     my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3142     $sth->execute( $frameworkcode, $biblionumber );
3143     $sth->finish;
3144     my $encoding = C4::Context->preference("marcflavour");
3145
3146     # deal with UNIMARC field 100 (encoding) : create it if needed & set encoding to unicode
3147     if ( $encoding eq "UNIMARC" ) {
3148         my $defaultlanguage = C4::Context->preference("UNIMARCField100Language");
3149         $defaultlanguage = "fre" if (!$defaultlanguage || length($defaultlanguage) != 3);
3150         my $string = $record->subfield( 100, "a" );
3151         if ( ($string) && ( length( $record->subfield( 100, "a" ) ) == 36 ) ) {
3152             my $f100 = $record->field(100);
3153             $record->delete_field($f100);
3154         } else {
3155             $string = POSIX::strftime( "%Y%m%d", localtime );
3156             $string =~ s/\-//g;
3157             $string = sprintf( "%-*s", 35, $string );
3158             substr ( $string, 22, 3, $defaultlanguage);
3159         }
3160         substr( $string, 25, 3, "y50" );
3161         unless ( $record->subfield( 100, "a" ) ) {
3162             $record->insert_fields_ordered( MARC::Field->new( 100, "", "", "a" => $string ) );
3163         }
3164     }
3165
3166     #enhancement 5374: update transaction date (005) for marc21/unimarc
3167     if($encoding =~ /MARC21|UNIMARC/) {
3168       my @a= (localtime) [5,4,3,2,1,0]; $a[0]+=1900; $a[1]++;
3169         # YY MM DD HH MM SS (update year and month)
3170       my $f005= $record->field('005');
3171       $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3172     }
3173
3174     my $metadata = {
3175         biblionumber => $biblionumber,
3176         format       => 'marcxml',
3177         schema       => C4::Context->preference('marcflavour'),
3178     };
3179     $record->as_usmarc; # Bug 20126/10455 This triggers field length calculation
3180
3181     my $m_rs = Koha::Biblio::Metadatas->find($metadata) //
3182         Koha::Biblio::Metadata->new($metadata);
3183
3184     my $userenv = C4::Context->userenv;
3185     if ($userenv) {
3186         my $borrowernumber = $userenv->{number};
3187         my $borrowername = join ' ', map { $_ // q{} } @$userenv{qw(firstname surname)};
3188         unless ($m_rs->in_storage) {
3189             Koha::Util::MARC::set_marc_field($record, C4::Context->preference('MarcFieldForCreatorId'), $borrowernumber);
3190             Koha::Util::MARC::set_marc_field($record, C4::Context->preference('MarcFieldForCreatorName'), $borrowername);
3191         }
3192         Koha::Util::MARC::set_marc_field($record, C4::Context->preference('MarcFieldForModifierId'), $borrowernumber);
3193         Koha::Util::MARC::set_marc_field($record, C4::Context->preference('MarcFieldForModifierName'), $borrowername);
3194     }
3195
3196     $m_rs->metadata( $record->as_xml_record($encoding) );
3197     $m_rs->store;
3198
3199     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
3200
3201     return $biblionumber;
3202 }
3203
3204 =head2 prepare_host_field
3205
3206 $marcfield = prepare_host_field( $hostbiblioitem, $marcflavour );
3207 Generate the host item entry for an analytic child entry
3208
3209 =cut
3210
3211 sub prepare_host_field {
3212     my ( $hostbiblio, $marcflavour ) = @_;
3213     $marcflavour ||= C4::Context->preference('marcflavour');
3214     my $host = GetMarcBiblio({ biblionumber => $hostbiblio });
3215     # unfortunately as_string does not 'do the right thing'
3216     # if field returns undef
3217     my %sfd;
3218     my $field;
3219     my $host_field;
3220     if ( $marcflavour eq 'MARC21' || $marcflavour eq 'NORMARC' ) {
3221         if ( $field = $host->field('100') || $host->field('110') || $host->field('11') ) {
3222             my $s = $field->as_string('ab');
3223             if ($s) {
3224                 $sfd{a} = $s;
3225             }
3226         }
3227         if ( $field = $host->field('245') ) {
3228             my $s = $field->as_string('a');
3229             if ($s) {
3230                 $sfd{t} = $s;
3231             }
3232         }
3233         if ( $field = $host->field('260') ) {
3234             my $s = $field->as_string('abc');
3235             if ($s) {
3236                 $sfd{d} = $s;
3237             }
3238         }
3239         if ( $field = $host->field('240') ) {
3240             my $s = $field->as_string();
3241             if ($s) {
3242                 $sfd{b} = $s;
3243             }
3244         }
3245         if ( $field = $host->field('022') ) {
3246             my $s = $field->as_string('a');
3247             if ($s) {
3248                 $sfd{x} = $s;
3249             }
3250         }
3251         if ( $field = $host->field('020') ) {
3252             my $s = $field->as_string('a');
3253             if ($s) {
3254                 $sfd{z} = $s;
3255             }
3256         }
3257         if ( $field = $host->field('001') ) {
3258             $sfd{w} = $field->data(),;
3259         }
3260         $host_field = MARC::Field->new( 773, '0', ' ', %sfd );
3261         return $host_field;
3262     }
3263     elsif ( $marcflavour eq 'UNIMARC' ) {
3264         #author
3265         if ( $field = $host->field('700') || $host->field('710') || $host->field('720') ) {
3266             my $s = $field->as_string('ab');
3267             if ($s) {
3268                 $sfd{a} = $s;
3269             }
3270         }
3271         #title
3272         if ( $field = $host->field('200') ) {
3273             my $s = $field->as_string('a');
3274             if ($s) {
3275                 $sfd{t} = $s;
3276             }
3277         }
3278         #place of publicaton
3279         if ( $field = $host->field('210') ) {
3280             my $s = $field->as_string('a');
3281             if ($s) {
3282                 $sfd{c} = $s;
3283             }
3284         }
3285         #date of publication
3286         if ( $field = $host->field('210') ) {
3287             my $s = $field->as_string('d');
3288             if ($s) {
3289                 $sfd{d} = $s;
3290             }
3291         }
3292         #edition statement
3293         if ( $field = $host->field('205') ) {
3294             my $s = $field->as_string();
3295             if ($s) {
3296                 $sfd{e} = $s;
3297             }
3298         }
3299         #URL
3300         if ( $field = $host->field('856') ) {
3301             my $s = $field->as_string('u');
3302             if ($s) {
3303                 $sfd{u} = $s;
3304             }
3305         }
3306         #ISSN
3307         if ( $field = $host->field('011') ) {
3308             my $s = $field->as_string('a');
3309             if ($s) {
3310                 $sfd{x} = $s;
3311             }
3312         }
3313         #ISBN
3314         if ( $field = $host->field('010') ) {
3315             my $s = $field->as_string('a');
3316             if ($s) {
3317                 $sfd{y} = $s;
3318             }
3319         }
3320         if ( $field = $host->field('001') ) {
3321             $sfd{0} = $field->data(),;
3322         }
3323         $host_field = MARC::Field->new( 461, '0', ' ', %sfd );
3324         return $host_field;
3325     }
3326     return;
3327 }
3328
3329
3330 =head2 UpdateTotalIssues
3331
3332   UpdateTotalIssues($biblionumber, $increase, [$value])
3333
3334 Update the total issue count for a particular bib record.
3335
3336 =over 4
3337
3338 =item C<$biblionumber> is the biblionumber of the bib to update
3339
3340 =item C<$increase> is the amount to increase (or decrease) the total issues count by
3341
3342 =item C<$value> is the absolute value that total issues count should be set to. If provided, C<$increase> is ignored.
3343
3344 =back
3345
3346 =cut
3347
3348 sub UpdateTotalIssues {
3349     my ($biblionumber, $increase, $value) = @_;
3350     my $totalissues;
3351
3352     my $record = GetMarcBiblio({ biblionumber => $biblionumber });
3353     unless ($record) {
3354         carp "UpdateTotalIssues could not get biblio record";
3355         return;
3356     }
3357     my $biblio = Koha::Biblios->find( $biblionumber );
3358     unless ($biblio) {
3359         carp "UpdateTotalIssues could not get datas of biblio";
3360         return;
3361     }
3362     my $biblioitem = $biblio->biblioitem;
3363     my ($totalissuestag, $totalissuessubfield) = GetMarcFromKohaField( 'biblioitems.totalissues' );
3364     unless ($totalissuestag) {
3365         return 1; # There is nothing to do
3366     }
3367
3368     if (defined $value) {
3369         $totalissues = $value;
3370     } else {
3371         $totalissues = $biblioitem->totalissues + $increase;
3372     }
3373
3374      my $field = $record->field($totalissuestag);
3375      if (defined $field) {
3376          $field->update( $totalissuessubfield => $totalissues );
3377      } else {
3378          $field = MARC::Field->new($totalissuestag, '0', '0',
3379                  $totalissuessubfield => $totalissues);
3380          $record->insert_grouped_field($field);
3381      }
3382
3383      return ModBiblio($record, $biblionumber, $biblio->frameworkcode);
3384 }
3385
3386 =head2 RemoveAllNsb
3387
3388     &RemoveAllNsb($record);
3389
3390 Removes all nsb/nse chars from a record
3391
3392 =cut
3393
3394 sub RemoveAllNsb {
3395     my $record = shift;
3396     if (!$record) {
3397         carp 'RemoveAllNsb called with undefined record';
3398         return;
3399     }
3400
3401     SetUTF8Flag($record);
3402
3403     foreach my $field ($record->fields()) {
3404         if ($field->is_control_field()) {
3405             $field->update(nsb_clean($field->data()));
3406         } else {
3407             my @subfields = $field->subfields();
3408             my @new_subfields;
3409             foreach my $subfield (@subfields) {
3410                 push @new_subfields, $subfield->[0] => nsb_clean($subfield->[1]);
3411             }
3412             if (scalar(@new_subfields) > 0) {
3413                 my $new_field;
3414                 eval {
3415                     $new_field = MARC::Field->new(
3416                         $field->tag(),
3417                         $field->indicator(1),
3418                         $field->indicator(2),
3419                         @new_subfields
3420                     );
3421                 };
3422                 if ($@) {
3423                     warn "error in RemoveAllNsb : $@";
3424                 } else {
3425                     $field->replace_with($new_field);
3426                 }
3427             }
3428         }
3429     }
3430
3431     return $record;
3432 }
3433
3434 1;
3435
3436
3437 =head2 _after_biblio_action_hooks
3438
3439 Helper method that takes care of calling all plugin hooks
3440
3441 =cut
3442
3443 sub _after_biblio_action_hooks {
3444     my ( $args ) = @_;
3445
3446     my $biblio_id = $args->{biblio_id};
3447     my $action    = $args->{action};
3448
3449     my $biblio = Koha::Biblios->find( $biblio_id );
3450     Koha::Plugins->call(
3451         'after_biblio_action',
3452         {
3453             action    => $action,
3454             biblio    => $biblio,
3455             biblio_id => $biblio_id,
3456         }
3457     );
3458 }
3459
3460 __END__
3461
3462 =head1 AUTHOR
3463
3464 Koha Development Team <http://koha-community.org/>
3465
3466 Paul POULAIN paul.poulain@free.fr
3467
3468 Joshua Ferraro jmf@liblime.com
3469
3470 =cut