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