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