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