Bug 23463: Remove uneeded SearchItemsByField subroutine
[koha.git] / C4 / Items.pm
1 package C4::Items;
2
3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use Modern::Perl;
22
23 use vars qw(@ISA @EXPORT);
24 BEGIN {
25     require Exporter;
26     @ISA = qw(Exporter);
27
28     @EXPORT = qw(
29         AddItemFromMarc
30         AddItem
31         AddItemBatchFromMarc
32         ModItemFromMarc
33         Item2Marc
34         ModItem
35         ModDateLastSeen
36         ModItemTransfer
37         DelItem
38         CheckItemPreSave
39         GetItemsForInventory
40         GetItemsInfo
41         GetItemsLocationInfo
42         GetHostItemsInfo
43         get_hostitemnumbers_of
44         GetHiddenItemnumbers
45         ItemSafeToDelete
46         DelItemCheck
47         MoveItemFromBiblio
48         CartToShelf
49         GetAnalyticsCount
50         SearchItems
51         PrepareItemrecordDisplay
52     );
53 }
54
55 use Carp;
56 use Try::Tiny;
57 use C4::Context;
58 use C4::Koha;
59 use C4::Biblio;
60 use Koha::DateUtils;
61 use MARC::Record;
62 use C4::ClassSource;
63 use C4::Log;
64 use List::MoreUtils qw(any);
65 use YAML qw(Load);
66 use DateTime::Format::MySQL;
67 use Data::Dumper; # used as part of logging item record changes, not just for
68                   # debugging; so please don't remove this
69
70 use Koha::AuthorisedValues;
71 use Koha::DateUtils qw(dt_from_string);
72 use Koha::Database;
73
74 use Koha::Biblioitems;
75 use Koha::Items;
76 use Koha::ItemTypes;
77 use Koha::SearchEngine;
78 use Koha::SearchEngine::Search;
79 use Koha::Libraries;
80
81 =head1 NAME
82
83 C4::Items - item management functions
84
85 =head1 DESCRIPTION
86
87 This module contains an API for manipulating item 
88 records in Koha, and is used by cataloguing, circulation,
89 acquisitions, and serials management.
90
91 # FIXME This POD is not up-to-date
92 A Koha item record is stored in two places: the
93 items table and embedded in a MARC tag in the XML
94 version of the associated bib record in C<biblioitems.marcxml>.
95 This is done to allow the item information to be readily
96 indexed (e.g., by Zebra), but means that each item
97 modification transaction must keep the items table
98 and the MARC XML in sync at all times.
99
100 The items table will be considered authoritative.  In other
101 words, if there is ever a discrepancy between the items
102 table and the MARC XML, the items table should be considered
103 accurate.
104
105 =head1 HISTORICAL NOTE
106
107 Most of the functions in C<C4::Items> were originally in
108 the C<C4::Biblio> module.
109
110 =head1 CORE EXPORTED FUNCTIONS
111
112 The following functions are meant for use by users
113 of C<C4::Items>
114
115 =cut
116
117 =head2 CartToShelf
118
119   CartToShelf($itemnumber);
120
121 Set the current shelving location of the item record
122 to its stored permanent shelving location.  This is
123 primarily used to indicate when an item whose current
124 location is a special processing ('PROC') or shelving cart
125 ('CART') location is back in the stacks.
126
127 =cut
128
129 sub CartToShelf {
130     my ( $itemnumber ) = @_;
131
132     unless ( $itemnumber ) {
133         croak "FAILED CartToShelf() - no itemnumber supplied";
134     }
135
136     my $item = Koha::Items->find($itemnumber);
137     if ( $item->location eq 'CART' ) {
138         ModItem({ location => $item->permanent_location}, undef, $itemnumber);
139     }
140 }
141
142 =head2 AddItemFromMarc
143
144   my ($biblionumber, $biblioitemnumber, $itemnumber) 
145       = AddItemFromMarc($source_item_marc, $biblionumber);
146
147 Given a MARC::Record object containing an embedded item
148 record and a biblionumber, create a new item record.
149
150 =cut
151
152 sub AddItemFromMarc {
153     my ( $source_item_marc, $biblionumber ) = @_;
154     my $dbh = C4::Context->dbh;
155
156     # parse item hash from MARC
157     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
158     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
159
160     my $localitemmarc = MARC::Record->new;
161     $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
162     my $item = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
163     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
164     return AddItem( $item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields );
165 }
166
167 =head2 AddItem
168
169   my ($biblionumber, $biblioitemnumber, $itemnumber) 
170       = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
171
172 Given a hash containing item column names as keys,
173 create a new Koha item record.
174
175 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
176 do not need to be supplied for general use; they exist
177 simply to allow them to be picked up from AddItemFromMarc.
178
179 The final optional parameter, C<$unlinked_item_subfields>, contains
180 an arrayref containing subfields present in the original MARC
181 representation of the item (e.g., from the item editor) that are
182 not mapped to C<items> columns directly but should instead
183 be stored in C<items.more_subfields_xml> and included in 
184 the biblio items tag for display and indexing.
185
186 =cut
187
188 sub AddItem {
189     my $item         = shift;
190     my $biblionumber = shift;
191
192     my $dbh           = @_ ? shift : C4::Context->dbh;
193     my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode($biblionumber);
194     my $unlinked_item_subfields;
195     if (@_) {
196         $unlinked_item_subfields = shift;
197     }
198
199     # needs old biblionumber and biblioitemnumber
200     $item->{'biblionumber'} = $biblionumber;
201     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
202     $sth->execute( $item->{'biblionumber'} );
203     ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
204
205     _set_defaults_for_add($item);
206     _set_derived_columns_for_add($item);
207     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
208
209     # FIXME - checks here
210     unless ( $item->{itype} ) {    # default to biblioitem.itemtype if no itype
211         my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
212         $itype_sth->execute( $item->{'biblionumber'} );
213         ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
214     }
215
216     my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
217     return if $error;
218
219     $item->{'itemnumber'} = $itemnumber;
220
221     C4::Biblio::ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
222
223     logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
224       if C4::Context->preference("CataloguingLog");
225
226     _after_item_action_hooks({ action => 'create', item_id => $itemnumber });
227
228     return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
229 }
230
231 =head2 AddItemBatchFromMarc
232
233   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
234              $biblionumber, $biblioitemnumber, $frameworkcode);
235
236 Efficiently create item records from a MARC biblio record with
237 embedded item fields.  This routine is suitable for batch jobs.
238
239 This API assumes that the bib record has already been
240 saved to the C<biblio> and C<biblioitems> tables.  It does
241 not expect that C<biblio_metadata.metadata> is populated, but it
242 will do so via a call to ModBibiloMarc.
243
244 The goal of this API is to have a similar effect to using AddBiblio
245 and AddItems in succession, but without inefficient repeated
246 parsing of the MARC XML bib record.
247
248 This function returns an arrayref of new itemsnumbers and an arrayref of item
249 errors encountered during the processing.  Each entry in the errors
250 list is a hashref containing the following keys:
251
252 =over
253
254 =item item_sequence
255
256 Sequence number of original item tag in the MARC record.
257
258 =item item_barcode
259
260 Item barcode, provide to assist in the construction of
261 useful error messages.
262
263 =item error_code
264
265 Code representing the error condition.  Can be 'duplicate_barcode',
266 'invalid_homebranch', or 'invalid_holdingbranch'.
267
268 =item error_information
269
270 Additional information appropriate to the error condition.
271
272 =back
273
274 =cut
275
276 sub AddItemBatchFromMarc {
277     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
278     my $error;
279     my @itemnumbers = ();
280     my @errors = ();
281     my $dbh = C4::Context->dbh;
282
283     # We modify the record, so lets work on a clone so we don't change the
284     # original.
285     $record = $record->clone();
286     # loop through the item tags and start creating items
287     my @bad_item_fields = ();
288     my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
289     my $item_sequence_num = 0;
290     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
291         $item_sequence_num++;
292         # we take the item field and stick it into a new
293         # MARC record -- this is required so far because (FIXME)
294         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
295         # and there is no TransformMarcFieldToKoha
296         my $temp_item_marc = MARC::Record->new();
297         $temp_item_marc->append_fields($item_field);
298     
299         # add biblionumber and biblioitemnumber
300         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
301         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
302         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
303         $item->{'biblionumber'} = $biblionumber;
304         $item->{'biblioitemnumber'} = $biblioitemnumber;
305
306         # check for duplicate barcode
307         my %item_errors = CheckItemPreSave($item);
308         if (%item_errors) {
309             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
310             push @bad_item_fields, $item_field;
311             next ITEMFIELD;
312         }
313
314         _set_defaults_for_add($item);
315         _set_derived_columns_for_add($item);
316         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
317         warn $error if $error;
318         push @itemnumbers, $itemnumber; # FIXME not checking error
319         $item->{'itemnumber'} = $itemnumber;
320
321         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
322
323         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
324         $item_field->replace_with($new_item_marc->field($itemtag));
325     }
326
327     # remove any MARC item fields for rejected items
328     foreach my $item_field (@bad_item_fields) {
329         $record->delete_field($item_field);
330     }
331
332     # update the MARC biblio
333  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
334
335     return (\@itemnumbers, \@errors);
336 }
337
338 =head2 ModItemFromMarc
339
340   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
341
342 This function updates an item record based on a supplied
343 C<MARC::Record> object containing an embedded item field.
344 This API is meant for the use of C<additem.pl>; for 
345 other purposes, C<ModItem> should be used.
346
347 This function uses the hash %default_values_for_mod_from_marc,
348 which contains default values for item fields to
349 apply when modifying an item.  This is needed because
350 if an item field's value is cleared, TransformMarcToKoha
351 does not include the column in the
352 hash that's passed to ModItem, which without
353 use of this hash makes it impossible to clear
354 an item field's value.  See bug 2466.
355
356 Note that only columns that can be directly
357 changed from the cataloging and serials
358 item editors are included in this hash.
359
360 Returns item record
361
362 =cut
363
364 sub _build_default_values_for_mod_marc {
365     # Has no framework parameter anymore, since Default is authoritative
366     # for Koha to MARC mappings.
367
368     my $cache     = Koha::Caches->get_instance();
369     my $cache_key = "default_value_for_mod_marc-";
370     my $cached    = $cache->get_from_cache($cache_key);
371     return $cached if $cached;
372
373     my $default_values = {
374         barcode                  => undef,
375         booksellerid             => undef,
376         ccode                    => undef,
377         'items.cn_source'        => undef,
378         coded_location_qualifier => undef,
379         copynumber               => undef,
380         damaged                  => 0,
381         enumchron                => undef,
382         holdingbranch            => undef,
383         homebranch               => undef,
384         itemcallnumber           => undef,
385         itemlost                 => 0,
386         itemnotes                => undef,
387         itemnotes_nonpublic      => undef,
388         itype                    => undef,
389         location                 => undef,
390         permanent_location       => undef,
391         materials                => undef,
392         new_status               => undef,
393         notforloan               => 0,
394         price                    => undef,
395         replacementprice         => undef,
396         replacementpricedate     => undef,
397         restricted               => undef,
398         stack                    => undef,
399         stocknumber              => undef,
400         uri                      => undef,
401         withdrawn                => 0,
402     };
403     my %default_values_for_mod_from_marc;
404     while ( my ( $field, $default_value ) = each %$default_values ) {
405         my $kohafield = $field;
406         $kohafield =~ s|^([^\.]+)$|items.$1|;
407         $default_values_for_mod_from_marc{$field} = $default_value
408             if C4::Biblio::GetMarcFromKohaField( $kohafield );
409     }
410
411     $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
412     return \%default_values_for_mod_from_marc;
413 }
414
415 sub ModItemFromMarc {
416     my $item_marc = shift;
417     my $biblionumber = shift;
418     my $itemnumber = shift;
419
420     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
421     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
422
423     my $localitemmarc = MARC::Record->new;
424     $localitemmarc->append_fields( $item_marc->field($itemtag) );
425     my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
426     my $default_values = _build_default_values_for_mod_marc();
427     foreach my $item_field ( keys %$default_values ) {
428         $item->{$item_field} = $default_values->{$item_field}
429           unless exists $item->{$item_field};
430     }
431     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
432
433     ModItem( $item, $biblionumber, $itemnumber, { unlinked_item_subfields => $unlinked_item_subfields } );
434     return $item;
435 }
436
437 =head2 ModItem
438
439 ModItem(
440     { column => $newvalue },
441     $biblionumber,
442     $itemnumber,
443     {
444         [ unlinked_item_subfields => $unlinked_item_subfields, ]
445         [ log_action => 1, ]
446     }
447 );
448
449 Change one or more columns in an item record.
450
451 The first argument is a hashref mapping from item column
452 names to the new values.  The second and third arguments
453 are the biblionumber and itemnumber, respectively.
454 The fourth, optional parameter (additional_params) may contain the keys
455 unlinked_item_subfields and log_action.
456
457 C<$unlinked_item_subfields> contains an arrayref containing
458 subfields present in the original MARC
459 representation of the item (e.g., from the item editor) that are
460 not mapped to C<items> columns directly but should instead
461 be stored in C<items.more_subfields_xml> and included in 
462 the biblio items tag for display and indexing.
463
464 If one of the changed columns is used to calculate
465 the derived value of a column such as C<items.cn_sort>, 
466 this routine will perform the necessary calculation
467 and set the value.
468
469 If log_action is set to false, the action will not be logged.
470 If log_action is true or undefined, the action will be logged.
471
472 =cut
473
474 sub ModItem {
475     my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
476     my $log_action = $additional_params->{log_action} // 1;
477     my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields};
478
479     return unless %$item;
480     $item->{'itemnumber'} = $itemnumber or return;
481
482     # if $biblionumber is undefined, get it from the current item
483     unless (defined $biblionumber) {
484         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
485     }
486
487     if ($unlinked_item_subfields) {
488         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
489     };
490
491     my @fields = qw( itemlost withdrawn damaged );
492
493     # Only retrieve the item if we need to set an "on" date field
494     if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
495         my $pre_mod_item = Koha::Items->find( $item->{'itemnumber'} );
496         for my $field (@fields) {
497             if (    defined( $item->{$field} )
498                 and not $pre_mod_item->$field
499                 and $item->{$field} )
500             {
501                 $item->{ $field . '_on' } =
502                   DateTime::Format::MySQL->format_datetime( dt_from_string() );
503             }
504         }
505     }
506
507     # If the field is defined but empty, we are removing and,
508     # and thus need to clear out the 'on' field as well
509     for my $field (@fields) {
510         if ( defined( $item->{$field} ) && !$item->{$field} ) {
511             $item->{ $field . '_on' } = undef;
512         }
513     }
514
515
516     _set_derived_columns_for_mod($item);
517     _do_column_fixes_for_mod($item);
518     # FIXME add checks
519     # duplicate barcode
520     # attempt to change itemnumber
521     # attempt to change biblionumber (if we want
522     # an API to relink an item to a different bib,
523     # it should be a separate function)
524
525     # update items table
526     _koha_modify_item($item);
527
528     # request that bib be reindexed so that searching on current
529     # item status is possible
530     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
531
532     _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
533
534     logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
535       if $log_action && C4::Context->preference("CataloguingLog");
536 }
537
538 =head2 ModItemTransfer
539
540   ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
541
542 Marks an item as being transferred from one branch to another and records the trigger.
543
544 =cut
545
546 sub ModItemTransfer {
547     my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
548
549     my $dbh = C4::Context->dbh;
550     my $item = Koha::Items->find( $itemnumber );
551
552     # Remove the 'shelving cart' location status if it is being used.
553     CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
554
555     $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
556
557     #new entry in branchtransfers....
558     my $sth = $dbh->prepare(
559         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
560         VALUES (?, ?, NOW(), ?, ?)");
561     $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
562
563     ModItem({ holdingbranch => $frombranch }, undef, $itemnumber, { log_action => 0 });
564     ModDateLastSeen($itemnumber);
565     return;
566 }
567
568 =head2 ModDateLastSeen
569
570 ModDateLastSeen( $itemnumber, $leave_item_lost );
571
572 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
573 C<$itemnumber> is the item number
574 C<$leave_item_lost> determines if a lost item will be found or remain lost
575
576 =cut
577
578 sub ModDateLastSeen {
579     my ( $itemnumber, $leave_item_lost ) = @_;
580
581     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
582
583     my $params;
584     $params->{datelastseen} = $today;
585     $params->{itemlost} = 0 unless $leave_item_lost;
586
587     ModItem( $params, undef, $itemnumber, { log_action => 0 } );
588 }
589
590 =head2 DelItem
591
592   DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
593
594 Exported function (core API) for deleting an item record in Koha.
595
596 =cut
597
598 sub DelItem {
599     my ( $params ) = @_;
600
601     my $itemnumber   = $params->{itemnumber};
602     my $biblionumber = $params->{biblionumber};
603
604     unless ($biblionumber) {
605         my $item = Koha::Items->find( $itemnumber );
606         $biblionumber = $item ? $item->biblio->biblionumber : undef;
607     }
608
609     # If there is no biblionumber for the given itemnumber, there is nothing to delete
610     return 0 unless $biblionumber;
611
612     # FIXME check the item has no current issues
613     my $deleted = _koha_delete_item( $itemnumber );
614
615     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
616
617     _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
618
619     #search item field code
620     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
621     return $deleted;
622 }
623
624 =head2 CheckItemPreSave
625
626     my $item_ref = TransformMarcToKoha($marc, 'items');
627     # do stuff
628     my %errors = CheckItemPreSave($item_ref);
629     if (exists $errors{'duplicate_barcode'}) {
630         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
631     } elsif (exists $errors{'invalid_homebranch'}) {
632         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
633     } elsif (exists $errors{'invalid_holdingbranch'}) {
634         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
635     } else {
636         print "item is OK";
637     }
638
639 Given a hashref containing item fields, determine if it can be
640 inserted or updated in the database.  Specifically, checks for
641 database integrity issues, and returns a hash containing any
642 of the following keys, if applicable.
643
644 =over 2
645
646 =item duplicate_barcode
647
648 Barcode, if it duplicates one already found in the database.
649
650 =item invalid_homebranch
651
652 Home branch, if not defined in branches table.
653
654 =item invalid_holdingbranch
655
656 Holding branch, if not defined in branches table.
657
658 =back
659
660 This function does NOT implement any policy-related checks,
661 e.g., whether current operator is allowed to save an
662 item that has a given branch code.
663
664 =cut
665
666 sub CheckItemPreSave {
667     my $item_ref = shift;
668
669     my %errors = ();
670
671     # check for duplicate barcode
672     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
673         my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
674         if ($existing_item) {
675             if (!exists $item_ref->{'itemnumber'}                       # new item
676                 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
677                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
678             }
679         }
680     }
681
682     # check for valid home branch
683     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
684         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
685         unless (defined $home_library) {
686             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
687         }
688     }
689
690     # check for valid holding branch
691     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
692         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
693         unless (defined $holding_library) {
694             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
695         }
696     }
697
698     return %errors;
699
700 }
701
702 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
703
704 The following functions provide various ways of 
705 getting an item record, a set of item records, or
706 lists of authorized values for certain item fields.
707
708 =cut
709
710 =head2 GetItemsForInventory
711
712 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
713   minlocation  => $minlocation,
714   maxlocation  => $maxlocation,
715   location     => $location,
716   itemtype     => $itemtype,
717   ignoreissued => $ignoreissued,
718   datelastseen => $datelastseen,
719   branchcode   => $branchcode,
720   branch       => $branch,
721   offset       => $offset,
722   size         => $size,
723   statushash   => $statushash,
724 } );
725
726 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
727
728 The sub returns a reference to a list of hashes, each containing
729 itemnumber, author, title, barcode, item callnumber, and date last
730 seen. It is ordered by callnumber then title.
731
732 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
733 the datelastseen can be used to specify that you want to see items not seen since a past date only.
734 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
735 $statushash requires a hashref that has the authorized values fieldname (intems.notforloan, etc...) as keys, and an arrayref of statuscodes we are searching for as values.
736
737 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
738
739 =cut
740
741 sub GetItemsForInventory {
742     my ( $parameters ) = @_;
743     my $minlocation  = $parameters->{'minlocation'}  // '';
744     my $maxlocation  = $parameters->{'maxlocation'}  // '';
745     my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
746     my $location     = $parameters->{'location'}     // '';
747     my $itemtype     = $parameters->{'itemtype'}     // '';
748     my $ignoreissued = $parameters->{'ignoreissued'} // '';
749     my $datelastseen = $parameters->{'datelastseen'} // '';
750     my $branchcode   = $parameters->{'branchcode'}   // '';
751     my $branch       = $parameters->{'branch'}       // '';
752     my $offset       = $parameters->{'offset'}       // '';
753     my $size         = $parameters->{'size'}         // '';
754     my $statushash   = $parameters->{'statushash'}   // '';
755     my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
756
757     my $dbh = C4::Context->dbh;
758     my ( @bind_params, @where_strings );
759
760     my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
761     my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
762
763     my $select_columns = q{
764         SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
765     };
766     my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
767     my $query = q{
768         FROM items
769         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
770         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
771     };
772     if ($statushash){
773         for my $authvfield (keys %$statushash){
774             if ( scalar @{$statushash->{$authvfield}} > 0 ){
775                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
776                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
777             }
778         }
779     }
780
781     if ($minlocation) {
782         push @where_strings, 'items.cn_sort >= ?';
783         push @bind_params, $min_cnsort;
784     }
785
786     if ($maxlocation) {
787         push @where_strings, 'items.cn_sort <= ?';
788         push @bind_params, $max_cnsort;
789     }
790
791     if ($datelastseen) {
792         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
793         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
794         push @bind_params, $datelastseen;
795     }
796
797     if ( $location ) {
798         push @where_strings, 'items.location = ?';
799         push @bind_params, $location;
800     }
801
802     if ( $branchcode ) {
803         if($branch eq "homebranch"){
804         push @where_strings, 'items.homebranch = ?';
805         }else{
806             push @where_strings, 'items.holdingbranch = ?';
807         }
808         push @bind_params, $branchcode;
809     }
810
811     if ( $itemtype ) {
812         push @where_strings, 'biblioitems.itemtype = ?';
813         push @bind_params, $itemtype;
814     }
815
816     if ( $ignoreissued) {
817         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
818         push @where_strings, 'issues.date_due IS NULL';
819     }
820
821     if ( $ignore_waiting_holds ) {
822         $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
823         push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
824     }
825
826     if ( @where_strings ) {
827         $query .= 'WHERE ';
828         $query .= join ' AND ', @where_strings;
829     }
830     my $count_query = $select_count . $query;
831     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
832     $query .= " LIMIT $offset, $size" if ($offset and $size);
833     $query = $select_columns . $query;
834     my $sth = $dbh->prepare($query);
835     $sth->execute( @bind_params );
836
837     my @results = ();
838     my $tmpresults = $sth->fetchall_arrayref({});
839     $sth = $dbh->prepare( $count_query );
840     $sth->execute( @bind_params );
841     my ($iTotalRecords) = $sth->fetchrow_array();
842
843     my @avs = Koha::AuthorisedValues->search(
844         {   'marc_subfield_structures.kohafield' => { '>' => '' },
845             'me.authorised_value'                => { '>' => '' },
846         },
847         {   join     => { category => 'marc_subfield_structures' },
848             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
849             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
850             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
851         }
852     );
853
854     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
855
856     foreach my $row (@$tmpresults) {
857
858         # Auth values
859         foreach (keys %$row) {
860             if (
861                 defined(
862                     $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
863                 )
864             ) {
865                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
866             }
867         }
868         push @results, $row;
869     }
870
871     return (\@results, $iTotalRecords);
872 }
873
874 =head2 GetItemsInfo
875
876   @results = GetItemsInfo($biblionumber);
877
878 Returns information about items with the given biblionumber.
879
880 C<GetItemsInfo> returns a list of references-to-hash. Each element
881 contains a number of keys. Most of them are attributes from the
882 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
883 Koha database. Other keys include:
884
885 =over 2
886
887 =item C<$data-E<gt>{branchname}>
888
889 The name (not the code) of the branch to which the book belongs.
890
891 =item C<$data-E<gt>{datelastseen}>
892
893 This is simply C<items.datelastseen>, except that while the date is
894 stored in YYYY-MM-DD format in the database, here it is converted to
895 DD/MM/YYYY format. A NULL date is returned as C<//>.
896
897 =item C<$data-E<gt>{datedue}>
898
899 =item C<$data-E<gt>{class}>
900
901 This is the concatenation of C<biblioitems.classification>, the book's
902 Dewey code, and C<biblioitems.subclass>.
903
904 =item C<$data-E<gt>{ocount}>
905
906 I think this is the number of copies of the book available.
907
908 =item C<$data-E<gt>{order}>
909
910 If this is set, it is set to C<One Order>.
911
912 =back
913
914 =cut
915
916 sub GetItemsInfo {
917     my ( $biblionumber ) = @_;
918     my $dbh   = C4::Context->dbh;
919     require C4::Languages;
920     my $language = C4::Languages::getlanguage();
921     my $query = "
922     SELECT items.*,
923            biblio.*,
924            biblioitems.volume,
925            biblioitems.number,
926            biblioitems.itemtype,
927            biblioitems.isbn,
928            biblioitems.issn,
929            biblioitems.publicationyear,
930            biblioitems.publishercode,
931            biblioitems.volumedate,
932            biblioitems.volumedesc,
933            biblioitems.lccn,
934            biblioitems.url,
935            items.notforloan as itemnotforloan,
936            issues.borrowernumber,
937            issues.date_due as datedue,
938            issues.onsite_checkout,
939            borrowers.cardnumber,
940            borrowers.surname,
941            borrowers.firstname,
942            borrowers.branchcode as bcode,
943            serial.serialseq,
944            serial.publisheddate,
945            itemtypes.description,
946            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
947            itemtypes.notforloan as notforloan_per_itemtype,
948            holding.branchurl,
949            holding.branchcode,
950            holding.branchname,
951            holding.opac_info as holding_branch_opac_info,
952            home.opac_info as home_branch_opac_info,
953            IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
954      FROM items
955      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
956      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
957      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
958      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
959      LEFT JOIN issues USING (itemnumber)
960      LEFT JOIN borrowers USING (borrowernumber)
961      LEFT JOIN serialitems USING (itemnumber)
962      LEFT JOIN serial USING (serialid)
963      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
964      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
965     $query .= q|
966     LEFT JOIN tmp_holdsqueue USING (itemnumber)
967     LEFT JOIN localization ON itemtypes.itemtype = localization.code
968         AND localization.entity = 'itemtypes'
969         AND localization.lang = ?
970     |;
971
972     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
973     my $sth = $dbh->prepare($query);
974     $sth->execute($language, $biblionumber);
975     my $i = 0;
976     my @results;
977     my $serial;
978
979     my $userenv = C4::Context->userenv;
980     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
981     while ( my $data = $sth->fetchrow_hashref ) {
982         if ( $data->{borrowernumber} && $want_not_same_branch) {
983             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
984         }
985
986         $serial ||= $data->{'serial'};
987
988         my $descriptions;
989         # get notforloan complete status if applicable
990         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
991         $data->{notforloanvalue}     = $descriptions->{lib} // '';
992         $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
993
994         # get restricted status and description if applicable
995         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
996         $data->{restrictedvalue}     = $descriptions->{lib} // '';
997         $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
998
999         # my stack procedures
1000         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
1001         $data->{stack}          = $descriptions->{lib} // '';
1002
1003         # Find the last 3 people who borrowed this item.
1004         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1005                                     WHERE itemnumber = ?
1006                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1007                                     ORDER BY returndate DESC
1008                                     LIMIT 3");
1009         $sth2->execute($data->{'itemnumber'});
1010         my $ii = 0;
1011         while (my $data2 = $sth2->fetchrow_hashref()) {
1012             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1013             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1014             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1015             $ii++;
1016         }
1017
1018         $results[$i] = $data;
1019         $i++;
1020     }
1021
1022     return $serial
1023         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1024         : @results;
1025 }
1026
1027 =head2 GetItemsLocationInfo
1028
1029   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1030
1031 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1032
1033 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1034
1035 =over 2
1036
1037 =item C<$data-E<gt>{homebranch}>
1038
1039 Branch Name of the item's homebranch
1040
1041 =item C<$data-E<gt>{holdingbranch}>
1042
1043 Branch Name of the item's holdingbranch
1044
1045 =item C<$data-E<gt>{location}>
1046
1047 Item's shelving location code
1048
1049 =item C<$data-E<gt>{location_intranet}>
1050
1051 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1052
1053 =item C<$data-E<gt>{location_opac}>
1054
1055 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
1056 description is set.
1057
1058 =item C<$data-E<gt>{itemcallnumber}>
1059
1060 Item's itemcallnumber
1061
1062 =item C<$data-E<gt>{cn_sort}>
1063
1064 Item's call number normalized for sorting
1065
1066 =back
1067   
1068 =cut
1069
1070 sub GetItemsLocationInfo {
1071         my $biblionumber = shift;
1072         my @results;
1073
1074         my $dbh = C4::Context->dbh;
1075         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
1076                             location, itemcallnumber, cn_sort
1077                      FROM items, branches as a, branches as b
1078                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
1079                      AND biblionumber = ?
1080                      ORDER BY cn_sort ASC";
1081         my $sth = $dbh->prepare($query);
1082         $sth->execute($biblionumber);
1083
1084         while ( my $data = $sth->fetchrow_hashref ) {
1085              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1086              $av = $av->count ? $av->next : undef;
1087              $data->{location_intranet} = $av ? $av->lib : '';
1088              $data->{location_opac}     = $av ? $av->opac_description : '';
1089              push @results, $data;
1090         }
1091         return @results;
1092 }
1093
1094 =head2 GetHostItemsInfo
1095
1096     $hostiteminfo = GetHostItemsInfo($hostfield);
1097     Returns the iteminfo for items linked to records via a host field
1098
1099 =cut
1100
1101 sub GetHostItemsInfo {
1102     my ($record) = @_;
1103     my @returnitemsInfo;
1104
1105     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1106         return @returnitemsInfo;
1107     }
1108
1109     my @fields;
1110     if( C4::Context->preference('marcflavour') eq 'MARC21' ||
1111       C4::Context->preference('marcflavour') eq 'NORMARC') {
1112         @fields = $record->field('773');
1113     } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
1114         @fields = $record->field('461');
1115     }
1116
1117     foreach my $hostfield ( @fields ) {
1118         my $hostbiblionumber = $hostfield->subfield("0");
1119         my $linkeditemnumber = $hostfield->subfield("9");
1120         my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1121         foreach my $hostitemInfo (@hostitemInfos) {
1122             if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
1123                 push @returnitemsInfo, $hostitemInfo;
1124                 last;
1125             }
1126         }
1127     }
1128     return @returnitemsInfo;
1129 }
1130
1131 =head2 get_hostitemnumbers_of
1132
1133   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1134
1135 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1136
1137 Return a reference on a hash where key is a biblionumber and values are
1138 references on array of itemnumbers.
1139
1140 =cut
1141
1142
1143 sub get_hostitemnumbers_of {
1144     my ($biblionumber) = @_;
1145
1146     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1147         return ();
1148     }
1149
1150     my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
1151     return unless $marcrecord;
1152
1153     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1154
1155     my $marcflavor = C4::Context->preference('marcflavour');
1156     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1157         $tag      = '773';
1158         $biblio_s = '0';
1159         $item_s   = '9';
1160     }
1161     elsif ( $marcflavor eq 'UNIMARC' ) {
1162         $tag      = '461';
1163         $biblio_s = '0';
1164         $item_s   = '9';
1165     }
1166
1167     foreach my $hostfield ( $marcrecord->field($tag) ) {
1168         my $hostbiblionumber = $hostfield->subfield($biblio_s);
1169         next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1170         my $linkeditemnumber = $hostfield->subfield($item_s);
1171         if ( ! $linkeditemnumber ) {
1172             warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1173             next;
1174         }
1175         my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1176         push @returnhostitemnumbers, $linkeditemnumber
1177           if $is_from_biblio;
1178     }
1179
1180     return @returnhostitemnumbers;
1181 }
1182
1183 =head2 GetHiddenItemnumbers
1184
1185     my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
1186
1187 Given a list of items it checks which should be hidden from the OPAC given
1188 the current configuration. Returns a list of itemnumbers corresponding to
1189 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
1190 to be excluded
1191
1192 =cut
1193
1194 sub GetHiddenItemnumbers {
1195     my $params = shift;
1196     my $items = $params->{items};
1197     if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1198         foreach my $except (split(/\|/, $exceptions)){
1199             if ($params->{'borcat'} eq $except){
1200                 return; # we don't hide anything for this borrower category
1201             }
1202         }
1203     }
1204     my @resultitems;
1205
1206     my $yaml = C4::Context->preference('OpacHiddenItems');
1207     return () if (! $yaml =~ /\S/ );
1208     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1209     my $hidingrules;
1210     eval {
1211         $hidingrules = YAML::Load($yaml);
1212     };
1213     if ($@) {
1214         warn "Unable to parse OpacHiddenItems syspref : $@";
1215         return ();
1216     }
1217     my $dbh = C4::Context->dbh;
1218
1219     # For each item
1220     foreach my $item (@$items) {
1221
1222         # We check each rule
1223         foreach my $field (keys %$hidingrules) {
1224             my $val;
1225             if (exists $item->{$field}) {
1226                 $val = $item->{$field};
1227             }
1228             else {
1229                 my $query = "SELECT $field from items where itemnumber = ?";
1230                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1231             }
1232             $val = '' unless defined $val;
1233
1234             # If the results matches the values in the yaml file
1235             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1236
1237                 # We add the itemnumber to the list
1238                 push @resultitems, $item->{'itemnumber'};
1239
1240                 # If at least one rule matched for an item, no need to test the others
1241                 last;
1242             }
1243         }
1244     }
1245     return @resultitems;
1246 }
1247
1248 =head1 LIMITED USE FUNCTIONS
1249
1250 The following functions, while part of the public API,
1251 are not exported.  This is generally because they are
1252 meant to be used by only one script for a specific
1253 purpose, and should not be used in any other context
1254 without careful thought.
1255
1256 =cut
1257
1258 =head2 GetMarcItem
1259
1260   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1261
1262 Returns MARC::Record of the item passed in parameter.
1263 This function is meant for use only in C<cataloguing/additem.pl>,
1264 where it is needed to support that script's MARC-like
1265 editor.
1266
1267 =cut
1268
1269 sub GetMarcItem {
1270     my ( $biblionumber, $itemnumber ) = @_;
1271
1272     # GetMarcItem has been revised so that it does the following:
1273     #  1. Gets the item information from the items table.
1274     #  2. Converts it to a MARC field for storage in the bib record.
1275     #
1276     # The previous behavior was:
1277     #  1. Get the bib record.
1278     #  2. Return the MARC tag corresponding to the item record.
1279     #
1280     # The difference is that one treats the items row as authoritative,
1281     # while the other treats the MARC representation as authoritative
1282     # under certain circumstances.
1283
1284     my $item = Koha::Items->find($itemnumber) or return;
1285
1286     # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1287     # Also, don't emit a subfield if the underlying field is blank.
1288
1289     return Item2Marc($item->unblessed, $biblionumber);
1290
1291 }
1292 sub Item2Marc {
1293         my ($itemrecord,$biblionumber)=@_;
1294     my $mungeditem = { 
1295         map {  
1296             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1297         } keys %{ $itemrecord } 
1298     };
1299     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1300     my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1301     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1302         "items.itemnumber", $framework,
1303     );
1304
1305     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1306     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1307                 foreach my $field ($itemmarc->field($itemtag)){
1308             $field->add_subfields(@$unlinked_item_subfields);
1309         }
1310     }
1311         return $itemmarc;
1312 }
1313
1314 =head1 PRIVATE FUNCTIONS AND VARIABLES
1315
1316 The following functions are not meant to be called
1317 directly, but are documented in order to explain
1318 the inner workings of C<C4::Items>.
1319
1320 =cut
1321
1322 =head2 %derived_columns
1323
1324 This hash keeps track of item columns that
1325 are strictly derived from other columns in
1326 the item record and are not meant to be set
1327 independently.
1328
1329 Each key in the hash should be the name of a
1330 column (as named by TransformMarcToKoha).  Each
1331 value should be hashref whose keys are the
1332 columns on which the derived column depends.  The
1333 hashref should also contain a 'BUILDER' key
1334 that is a reference to a sub that calculates
1335 the derived value.
1336
1337 =cut
1338
1339 my %derived_columns = (
1340     'items.cn_sort' => {
1341         'itemcallnumber' => 1,
1342         'items.cn_source' => 1,
1343         'BUILDER' => \&_calc_items_cn_sort,
1344     }
1345 );
1346
1347 =head2 _set_derived_columns_for_add 
1348
1349   _set_derived_column_for_add($item);
1350
1351 Given an item hash representing a new item to be added,
1352 calculate any derived columns.  Currently the only
1353 such column is C<items.cn_sort>.
1354
1355 =cut
1356
1357 sub _set_derived_columns_for_add {
1358     my $item = shift;
1359
1360     foreach my $column (keys %derived_columns) {
1361         my $builder = $derived_columns{$column}->{'BUILDER'};
1362         my $source_values = {};
1363         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1364             next if $source_column eq 'BUILDER';
1365             $source_values->{$source_column} = $item->{$source_column};
1366         }
1367         $builder->($item, $source_values);
1368     }
1369 }
1370
1371 =head2 _set_derived_columns_for_mod 
1372
1373   _set_derived_column_for_mod($item);
1374
1375 Given an item hash representing a new item to be modified.
1376 calculate any derived columns.  Currently the only
1377 such column is C<items.cn_sort>.
1378
1379 This routine differs from C<_set_derived_columns_for_add>
1380 in that it needs to handle partial item records.  In other
1381 words, the caller of C<ModItem> may have supplied only one
1382 or two columns to be changed, so this function needs to
1383 determine whether any of the columns to be changed affect
1384 any of the derived columns.  Also, if a derived column
1385 depends on more than one column, but the caller is not
1386 changing all of then, this routine retrieves the unchanged
1387 values from the database in order to ensure a correct
1388 calculation.
1389
1390 =cut
1391
1392 sub _set_derived_columns_for_mod {
1393     my $item = shift;
1394
1395     foreach my $column (keys %derived_columns) {
1396         my $builder = $derived_columns{$column}->{'BUILDER'};
1397         my $source_values = {};
1398         my %missing_sources = ();
1399         my $must_recalc = 0;
1400         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1401             next if $source_column eq 'BUILDER';
1402             if (exists $item->{$source_column}) {
1403                 $must_recalc = 1;
1404                 $source_values->{$source_column} = $item->{$source_column};
1405             } else {
1406                 $missing_sources{$source_column} = 1;
1407             }
1408         }
1409         if ($must_recalc) {
1410             foreach my $source_column (keys %missing_sources) {
1411                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1412             }
1413             $builder->($item, $source_values);
1414         }
1415     }
1416 }
1417
1418 =head2 _do_column_fixes_for_mod
1419
1420   _do_column_fixes_for_mod($item);
1421
1422 Given an item hashref containing one or more
1423 columns to modify, fix up certain values.
1424 Specifically, set to 0 any passed value
1425 of C<notforloan>, C<damaged>, C<itemlost>, or
1426 C<withdrawn> that is either undefined or
1427 contains the empty string.
1428
1429 =cut
1430
1431 sub _do_column_fixes_for_mod {
1432     my $item = shift;
1433
1434     if (exists $item->{'notforloan'} and
1435         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1436         $item->{'notforloan'} = 0;
1437     }
1438     if (exists $item->{'damaged'} and
1439         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1440         $item->{'damaged'} = 0;
1441     }
1442     if (exists $item->{'itemlost'} and
1443         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1444         $item->{'itemlost'} = 0;
1445     }
1446     if (exists $item->{'withdrawn'} and
1447         (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1448         $item->{'withdrawn'} = 0;
1449     }
1450     if (
1451         exists $item->{location}
1452         and ( !defined $item->{location}
1453             || ( $item->{location} ne 'CART' and $item->{location} ne 'PROC' ) )
1454         and not $item->{permanent_location}
1455       )
1456     {
1457         $item->{'permanent_location'} = $item->{'location'};
1458     }
1459     if (exists $item->{'timestamp'}) {
1460         delete $item->{'timestamp'};
1461     }
1462 }
1463
1464 =head2 _get_single_item_column
1465
1466   _get_single_item_column($column, $itemnumber);
1467
1468 Retrieves the value of a single column from an C<items>
1469 row specified by C<$itemnumber>.
1470
1471 =cut
1472
1473 sub _get_single_item_column {
1474     my $column = shift;
1475     my $itemnumber = shift;
1476     
1477     my $dbh = C4::Context->dbh;
1478     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1479     $sth->execute($itemnumber);
1480     my ($value) = $sth->fetchrow();
1481     return $value; 
1482 }
1483
1484 =head2 _calc_items_cn_sort
1485
1486   _calc_items_cn_sort($item, $source_values);
1487
1488 Helper routine to calculate C<items.cn_sort>.
1489
1490 =cut
1491
1492 sub _calc_items_cn_sort {
1493     my $item = shift;
1494     my $source_values = shift;
1495
1496     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1497 }
1498
1499 =head2 _set_defaults_for_add 
1500
1501   _set_defaults_for_add($item_hash);
1502
1503 Given an item hash representing an item to be added, set
1504 correct default values for columns whose default value
1505 is not handled by the DBMS.  This includes the following
1506 columns:
1507
1508 =over 2
1509
1510 =item * 
1511
1512 C<items.dateaccessioned>
1513
1514 =item *
1515
1516 C<items.notforloan>
1517
1518 =item *
1519
1520 C<items.damaged>
1521
1522 =item *
1523
1524 C<items.itemlost>
1525
1526 =item *
1527
1528 C<items.withdrawn>
1529
1530 =back
1531
1532 =cut
1533
1534 sub _set_defaults_for_add {
1535     my $item = shift;
1536     $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1537     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1538 }
1539
1540 =head2 _koha_new_item
1541
1542   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1543
1544 Perform the actual insert into the C<items> table.
1545
1546 =cut
1547
1548 sub _koha_new_item {
1549     my ( $item, $barcode ) = @_;
1550     my $dbh=C4::Context->dbh;  
1551     my $error;
1552     $item->{permanent_location} //= $item->{location};
1553     _mod_item_dates( $item );
1554     my $query =
1555            "INSERT INTO items SET
1556             biblionumber        = ?,
1557             biblioitemnumber    = ?,
1558             barcode             = ?,
1559             dateaccessioned     = ?,
1560             booksellerid        = ?,
1561             homebranch          = ?,
1562             price               = ?,
1563             replacementprice    = ?,
1564             replacementpricedate = ?,
1565             datelastborrowed    = ?,
1566             datelastseen        = ?,
1567             stack               = ?,
1568             notforloan          = ?,
1569             damaged             = ?,
1570             itemlost            = ?,
1571             withdrawn           = ?,
1572             itemcallnumber      = ?,
1573             coded_location_qualifier = ?,
1574             restricted          = ?,
1575             itemnotes           = ?,
1576             itemnotes_nonpublic = ?,
1577             holdingbranch       = ?,
1578             location            = ?,
1579             permanent_location  = ?,
1580             onloan              = ?,
1581             issues              = ?,
1582             renewals            = ?,
1583             reserves            = ?,
1584             cn_source           = ?,
1585             cn_sort             = ?,
1586             ccode               = ?,
1587             itype               = ?,
1588             materials           = ?,
1589             uri                 = ?,
1590             enumchron           = ?,
1591             more_subfields_xml  = ?,
1592             copynumber          = ?,
1593             stocknumber         = ?,
1594             new_status          = ?
1595           ";
1596     my $sth = $dbh->prepare($query);
1597     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1598    $sth->execute(
1599             $item->{'biblionumber'},
1600             $item->{'biblioitemnumber'},
1601             $barcode,
1602             $item->{'dateaccessioned'},
1603             $item->{'booksellerid'},
1604             $item->{'homebranch'},
1605             $item->{'price'},
1606             $item->{'replacementprice'},
1607             $item->{'replacementpricedate'} || $today,
1608             $item->{datelastborrowed},
1609             $item->{datelastseen} || $today,
1610             $item->{stack},
1611             $item->{'notforloan'},
1612             $item->{'damaged'},
1613             $item->{'itemlost'},
1614             $item->{'withdrawn'},
1615             $item->{'itemcallnumber'},
1616             $item->{'coded_location_qualifier'},
1617             $item->{'restricted'},
1618             $item->{'itemnotes'},
1619             $item->{'itemnotes_nonpublic'},
1620             $item->{'holdingbranch'},
1621             $item->{'location'},
1622             $item->{'permanent_location'},
1623             $item->{'onloan'},
1624             $item->{'issues'},
1625             $item->{'renewals'},
1626             $item->{'reserves'},
1627             $item->{'items.cn_source'},
1628             $item->{'items.cn_sort'},
1629             $item->{'ccode'},
1630             $item->{'itype'},
1631             $item->{'materials'},
1632             $item->{'uri'},
1633             $item->{'enumchron'},
1634             $item->{'more_subfields_xml'},
1635             $item->{'copynumber'},
1636             $item->{'stocknumber'},
1637             $item->{'new_status'},
1638     );
1639
1640     my $itemnumber;
1641     if ( defined $sth->errstr ) {
1642         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1643     }
1644     else {
1645         $itemnumber = $dbh->{'mysql_insertid'};
1646     }
1647
1648     return ( $itemnumber, $error );
1649 }
1650
1651 =head2 MoveItemFromBiblio
1652
1653   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1654
1655 Moves an item from a biblio to another
1656
1657 Returns undef if the move failed or the biblionumber of the destination record otherwise
1658
1659 =cut
1660
1661 sub MoveItemFromBiblio {
1662     my ($itemnumber, $frombiblio, $tobiblio) = @_;
1663     my $dbh = C4::Context->dbh;
1664     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1665         SELECT biblioitemnumber
1666         FROM biblioitems
1667         WHERE biblionumber = ?
1668     |, undef, $tobiblio );
1669     my $return = $dbh->do(q|
1670         UPDATE items
1671         SET biblioitemnumber = ?,
1672             biblionumber = ?
1673         WHERE itemnumber = ?
1674             AND biblionumber = ?
1675     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1676     if ($return == 1) {
1677         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1678         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1679             # Checking if the item we want to move is in an order 
1680         require C4::Acquisition;
1681         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1682             if ($order) {
1683                     # Replacing the biblionumber within the order if necessary
1684                     $order->{'biblionumber'} = $tobiblio;
1685                 C4::Acquisition::ModOrder($order);
1686             }
1687
1688         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1689         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1690             $dbh->do( qq|
1691                 UPDATE $table_name
1692                 SET biblionumber = ?
1693                 WHERE itemnumber = ?
1694             |, undef, $tobiblio, $itemnumber );
1695         }
1696         return $tobiblio;
1697         }
1698     return;
1699 }
1700
1701 =head2 ItemSafeToDelete
1702
1703    ItemSafeToDelete( $biblionumber, $itemnumber);
1704
1705 Exported function (core API) for checking whether an item record is safe to delete.
1706
1707 returns 1 if the item is safe to delete,
1708
1709 "book_on_loan" if the item is checked out,
1710
1711 "not_same_branch" if the item is blocked by independent branches,
1712
1713 "book_reserved" if the there are holds aganst the item, or
1714
1715 "linked_analytics" if the item has linked analytic records.
1716
1717 =cut
1718
1719 sub ItemSafeToDelete {
1720     my ( $biblionumber, $itemnumber ) = @_;
1721     my $status;
1722     my $dbh = C4::Context->dbh;
1723
1724     my $error;
1725
1726     my $countanalytics = GetAnalyticsCount($itemnumber);
1727
1728     my $item = Koha::Items->find($itemnumber) or return;
1729
1730     if ($item->checkout) {
1731         $status = "book_on_loan";
1732     }
1733     elsif ( defined C4::Context->userenv
1734         and !C4::Context->IsSuperLibrarian()
1735         and C4::Context->preference("IndependentBranches")
1736         and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1737     {
1738         $status = "not_same_branch";
1739     }
1740     else {
1741         # check it doesn't have a waiting reserve
1742         my $sth = $dbh->prepare(
1743             q{
1744             SELECT COUNT(*) FROM reserves
1745             WHERE (found = 'W' OR found = 'T')
1746             AND itemnumber = ?
1747         }
1748         );
1749         $sth->execute($itemnumber);
1750         my ($reserve) = $sth->fetchrow;
1751         if ($reserve) {
1752             $status = "book_reserved";
1753         }
1754         elsif ( $countanalytics > 0 ) {
1755             $status = "linked_analytics";
1756         }
1757         else {
1758             $status = 1;
1759         }
1760     }
1761     return $status;
1762 }
1763
1764 =head2 DelItemCheck
1765
1766    DelItemCheck( $biblionumber, $itemnumber);
1767
1768 Exported function (core API) for deleting an item record in Koha if there no current issue.
1769
1770 DelItemCheck wraps ItemSafeToDelete around DelItem.
1771
1772 =cut
1773
1774 sub DelItemCheck {
1775     my ( $biblionumber, $itemnumber ) = @_;
1776     my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1777
1778     if ( $status == 1 ) {
1779         DelItem(
1780             {
1781                 biblionumber => $biblionumber,
1782                 itemnumber   => $itemnumber
1783             }
1784         );
1785     }
1786     return $status;
1787 }
1788
1789 =head2 _koha_modify_item
1790
1791   my ($itemnumber,$error) =_koha_modify_item( $item );
1792
1793 Perform the actual update of the C<items> row.  Note that this
1794 routine accepts a hashref specifying the columns to update.
1795
1796 =cut
1797
1798 sub _koha_modify_item {
1799     my ( $item ) = @_;
1800     my $dbh=C4::Context->dbh;  
1801     my $error;
1802
1803     my $query = "UPDATE items SET ";
1804     my @bind;
1805     _mod_item_dates( $item );
1806     for my $key ( keys %$item ) {
1807         next if ( $key eq 'itemnumber' );
1808         $query.="$key=?,";
1809         push @bind, $item->{$key};
1810     }
1811     $query =~ s/,$//;
1812     $query .= " WHERE itemnumber=?";
1813     push @bind, $item->{'itemnumber'};
1814     my $sth = $dbh->prepare($query);
1815     $sth->execute(@bind);
1816     if ( $sth->err ) {
1817         $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
1818         warn $error;
1819     }
1820     return ($item->{'itemnumber'},$error);
1821 }
1822
1823 sub _mod_item_dates { # date formatting for date fields in item hash
1824     my ( $item ) = @_;
1825     return if !$item || ref($item) ne 'HASH';
1826
1827     my @keys = grep
1828         { $_ =~ /^onloan$|^date|date$|datetime$/ }
1829         keys %$item;
1830     # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1831     # NOTE: We do not (yet) have items fields ending with datetime
1832     # Fields with _on$ have been handled already
1833
1834     foreach my $key ( @keys ) {
1835         next if !defined $item->{$key}; # skip undefs
1836         my $dt = eval { dt_from_string( $item->{$key} ) };
1837             # eval: dt_from_string will die on us if we pass illegal dates
1838
1839         my $newstr;
1840         if( defined $dt  && ref($dt) eq 'DateTime' ) {
1841             if( $key =~ /datetime/ ) {
1842                 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1843             } else {
1844                 $newstr = DateTime::Format::MySQL->format_date($dt);
1845             }
1846         }
1847         $item->{$key} = $newstr; # might be undef to clear garbage
1848     }
1849 }
1850
1851 =head2 _koha_delete_item
1852
1853   _koha_delete_item( $itemnum );
1854
1855 Internal function to delete an item record from the koha tables
1856
1857 =cut
1858
1859 sub _koha_delete_item {
1860     my ( $itemnum ) = @_;
1861
1862     my $dbh = C4::Context->dbh;
1863     # save the deleted item to deleteditems table
1864     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1865     $sth->execute($itemnum);
1866     my $data = $sth->fetchrow_hashref();
1867
1868     # There is no item to delete
1869     return 0 unless $data;
1870
1871     my $query = "INSERT INTO deleteditems SET ";
1872     my @bind  = ();
1873     foreach my $key ( keys %$data ) {
1874         next if ( $key eq 'timestamp' ); # timestamp will be set by db
1875         $query .= "$key = ?,";
1876         push( @bind, $data->{$key} );
1877     }
1878     $query =~ s/\,$//;
1879     $sth = $dbh->prepare($query);
1880     $sth->execute(@bind);
1881
1882     # delete from items table
1883     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1884     my $deleted = $sth->execute($itemnum);
1885     return ( $deleted == 1 ) ? 1 : 0;
1886 }
1887
1888 =head2 _marc_from_item_hash
1889
1890   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1891
1892 Given an item hash representing a complete item record,
1893 create a C<MARC::Record> object containing an embedded
1894 tag representing that item.
1895
1896 The third, optional parameter C<$unlinked_item_subfields> is
1897 an arrayref of subfields (not mapped to C<items> fields per the
1898 framework) to be added to the MARC representation
1899 of the item.
1900
1901 =cut
1902
1903 sub _marc_from_item_hash {
1904     my $item = shift;
1905     my $frameworkcode = shift;
1906     my $unlinked_item_subfields;
1907     if (@_) {
1908         $unlinked_item_subfields = shift;
1909     }
1910    
1911     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1912     # Also, don't emit a subfield if the underlying field is blank.
1913     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1914                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1915                                 : ()  } keys %{ $item } }; 
1916
1917     my $item_marc = MARC::Record->new();
1918     foreach my $item_field ( keys %{$mungeditem} ) {
1919         my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1920         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
1921         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1922         foreach my $value (@values){
1923             if ( my $field = $item_marc->field($tag) ) {
1924                     $field->add_subfields( $subfield => $value );
1925             } else {
1926                 my $add_subfields = [];
1927                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1928                     $add_subfields = $unlinked_item_subfields;
1929             }
1930             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1931             }
1932         }
1933     }
1934
1935     return $item_marc;
1936 }
1937
1938 =head2 _repack_item_errors
1939
1940 Add an error message hash generated by C<CheckItemPreSave>
1941 to a list of errors.
1942
1943 =cut
1944
1945 sub _repack_item_errors {
1946     my $item_sequence_num = shift;
1947     my $item_ref = shift;
1948     my $error_ref = shift;
1949
1950     my @repacked_errors = ();
1951
1952     foreach my $error_code (sort keys %{ $error_ref }) {
1953         my $repacked_error = {};
1954         $repacked_error->{'item_sequence'} = $item_sequence_num;
1955         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1956         $repacked_error->{'error_code'} = $error_code;
1957         $repacked_error->{'error_information'} = $error_ref->{$error_code};
1958         push @repacked_errors, $repacked_error;
1959     } 
1960
1961     return @repacked_errors;
1962 }
1963
1964 =head2 _get_unlinked_item_subfields
1965
1966   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1967
1968 =cut
1969
1970 sub _get_unlinked_item_subfields {
1971     my $original_item_marc = shift;
1972     my $frameworkcode = shift;
1973
1974     my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1975
1976     # assume that this record has only one field, and that that
1977     # field contains only the item information
1978     my $subfields = [];
1979     my @fields = $original_item_marc->fields();
1980     if ($#fields > -1) {
1981         my $field = $fields[0];
1982             my $tag = $field->tag();
1983         foreach my $subfield ($field->subfields()) {
1984             if (defined $subfield->[1] and
1985                 $subfield->[1] ne '' and
1986                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1987                 push @$subfields, $subfield->[0] => $subfield->[1];
1988             }
1989         }
1990     }
1991     return $subfields;
1992 }
1993
1994 =head2 _get_unlinked_subfields_xml
1995
1996   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1997
1998 =cut
1999
2000 sub _get_unlinked_subfields_xml {
2001     my $unlinked_item_subfields = shift;
2002
2003     my $xml;
2004     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2005         my $marc = MARC::Record->new();
2006         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2007         # used in the framework
2008         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2009         $marc->encoding("UTF-8");    
2010         $xml = $marc->as_xml("USMARC");
2011     }
2012
2013     return $xml;
2014 }
2015
2016 =head2 _parse_unlinked_item_subfields_from_xml
2017
2018   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2019
2020 =cut
2021
2022 sub  _parse_unlinked_item_subfields_from_xml {
2023     my $xml = shift;
2024     require C4::Charset;
2025     return unless defined $xml and $xml ne "";
2026     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2027     my $unlinked_subfields = [];
2028     my @fields = $marc->fields();
2029     if ($#fields > -1) {
2030         foreach my $subfield ($fields[0]->subfields()) {
2031             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2032         }
2033     }
2034     return $unlinked_subfields;
2035 }
2036
2037 =head2 GetAnalyticsCount
2038
2039   $count= &GetAnalyticsCount($itemnumber)
2040
2041 counts Usage of itemnumber in Analytical bibliorecords. 
2042
2043 =cut
2044
2045 sub GetAnalyticsCount {
2046     my ($itemnumber) = @_;
2047
2048     ### ZOOM search here
2049     my $query;
2050     $query= "hi=".$itemnumber;
2051     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2052     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2053     return ($result);
2054 }
2055
2056 sub _SearchItems_build_where_fragment {
2057     my ($filter) = @_;
2058
2059     my $dbh = C4::Context->dbh;
2060
2061     my $where_fragment;
2062     if (exists($filter->{conjunction})) {
2063         my (@where_strs, @where_args);
2064         foreach my $f (@{ $filter->{filters} }) {
2065             my $fragment = _SearchItems_build_where_fragment($f);
2066             if ($fragment) {
2067                 push @where_strs, $fragment->{str};
2068                 push @where_args, @{ $fragment->{args} };
2069             }
2070         }
2071         my $where_str = '';
2072         if (@where_strs) {
2073             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2074             $where_fragment = {
2075                 str => $where_str,
2076                 args => \@where_args,
2077             };
2078         }
2079     } else {
2080         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2081         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2082         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2083         my @operators = qw(= != > < >= <= like);
2084         my $field = $filter->{field} // q{};
2085         if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2086             my $op = $filter->{operator};
2087             my $query = $filter->{query};
2088
2089             if (!$op or (0 == grep { $_ eq $op } @operators)) {
2090                 $op = '='; # default operator
2091             }
2092
2093             my $column;
2094             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2095                 my $marcfield = $1;
2096                 my $marcsubfield = $2;
2097                 my ($kohafield) = $dbh->selectrow_array(q|
2098                     SELECT kohafield FROM marc_subfield_structure
2099                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2100                 |, undef, $marcfield, $marcsubfield);
2101
2102                 if ($kohafield) {
2103                     $column = $kohafield;
2104                 } else {
2105                     # MARC field is not linked to a DB field so we need to use
2106                     # ExtractValue on marcxml from biblio_metadata or
2107                     # items.more_subfields_xml, depending on the MARC field.
2108                     my $xpath;
2109                     my $sqlfield;
2110                     my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2111                     if ($marcfield eq $itemfield) {
2112                         $sqlfield = 'more_subfields_xml';
2113                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2114                     } else {
2115                         $sqlfield = 'metadata'; # From biblio_metadata
2116                         if ($marcfield < 10) {
2117                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2118                         } else {
2119                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2120                         }
2121                     }
2122                     $column = "ExtractValue($sqlfield, '$xpath')";
2123                 }
2124             } elsif ($field eq 'issues') {
2125                 # Consider NULL as 0 for issues count
2126                 $column = 'COALESCE(issues,0)';
2127             } else {
2128                 $column = $field;
2129             }
2130
2131             if (ref $query eq 'ARRAY') {
2132                 if ($op eq '=') {
2133                     $op = 'IN';
2134                 } elsif ($op eq '!=') {
2135                     $op = 'NOT IN';
2136                 }
2137                 $where_fragment = {
2138                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
2139                     args => $query,
2140                 };
2141             } else {
2142                 $where_fragment = {
2143                     str => "$column $op ?",
2144                     args => [ $query ],
2145                 };
2146             }
2147         }
2148     }
2149
2150     return $where_fragment;
2151 }
2152
2153 =head2 SearchItems
2154
2155     my ($items, $total) = SearchItems($filter, $params);
2156
2157 Perform a search among items
2158
2159 $filter is a reference to a hash which can be a filter, or a combination of filters.
2160
2161 A filter has the following keys:
2162
2163 =over 2
2164
2165 =item * field: the name of a SQL column in table items
2166
2167 =item * query: the value to search in this column
2168
2169 =item * operator: comparison operator. Can be one of = != > < >= <= like
2170
2171 =back
2172
2173 A combination of filters hash the following keys:
2174
2175 =over 2
2176
2177 =item * conjunction: 'AND' or 'OR'
2178
2179 =item * filters: array ref of filters
2180
2181 =back
2182
2183 $params is a reference to a hash that can contain the following parameters:
2184
2185 =over 2
2186
2187 =item * rows: Number of items to return. 0 returns everything (default: 0)
2188
2189 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2190                (default: 1)
2191
2192 =item * sortby: A SQL column name in items table to sort on
2193
2194 =item * sortorder: 'ASC' or 'DESC'
2195
2196 =back
2197
2198 =cut
2199
2200 sub SearchItems {
2201     my ($filter, $params) = @_;
2202
2203     $filter //= {};
2204     $params //= {};
2205     return unless ref $filter eq 'HASH';
2206     return unless ref $params eq 'HASH';
2207
2208     # Default parameters
2209     $params->{rows} ||= 0;
2210     $params->{page} ||= 1;
2211     $params->{sortby} ||= 'itemnumber';
2212     $params->{sortorder} ||= 'ASC';
2213
2214     my ($where_str, @where_args);
2215     my $where_fragment = _SearchItems_build_where_fragment($filter);
2216     if ($where_fragment) {
2217         $where_str = $where_fragment->{str};
2218         @where_args = @{ $where_fragment->{args} };
2219     }
2220
2221     my $dbh = C4::Context->dbh;
2222     my $query = q{
2223         SELECT SQL_CALC_FOUND_ROWS items.*
2224         FROM items
2225           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2226           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2227           LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2228           WHERE 1
2229     };
2230     if (defined $where_str and $where_str ne '') {
2231         $query .= qq{ AND $where_str };
2232     }
2233
2234     $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
2235     push @where_args, C4::Context->preference('marcflavour');
2236
2237     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2238     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2239     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2240     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2241         ? $params->{sortby} : 'itemnumber';
2242     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2243     $query .= qq{ ORDER BY $sortby $sortorder };
2244
2245     my $rows = $params->{rows};
2246     my @limit_args;
2247     if ($rows > 0) {
2248         my $offset = $rows * ($params->{page}-1);
2249         $query .= qq { LIMIT ?, ? };
2250         push @limit_args, $offset, $rows;
2251     }
2252
2253     my $sth = $dbh->prepare($query);
2254     my $rv = $sth->execute(@where_args, @limit_args);
2255
2256     return unless ($rv);
2257     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2258
2259     return ($sth->fetchall_arrayref({}), $total_rows);
2260 }
2261
2262
2263 =head1  OTHER FUNCTIONS
2264
2265 =head2 _find_value
2266
2267   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2268
2269 Find the given $subfield in the given $tag in the given
2270 MARC::Record $record.  If the subfield is found, returns
2271 the (indicators, value) pair; otherwise, (undef, undef) is
2272 returned.
2273
2274 PROPOSITION :
2275 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2276 I suggest we export it from this module.
2277
2278 =cut
2279
2280 sub _find_value {
2281     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2282     my @result;
2283     my $indicator;
2284     if ( $tagfield < 10 ) {
2285         if ( $record->field($tagfield) ) {
2286             push @result, $record->field($tagfield)->data();
2287         } else {
2288             push @result, "";
2289         }
2290     } else {
2291         foreach my $field ( $record->field($tagfield) ) {
2292             my @subfields = $field->subfields();
2293             foreach my $subfield (@subfields) {
2294                 if ( @$subfield[0] eq $insubfield ) {
2295                     push @result, @$subfield[1];
2296                     $indicator = $field->indicator(1) . $field->indicator(2);
2297                 }
2298             }
2299         }
2300     }
2301     return ( $indicator, @result );
2302 }
2303
2304
2305 =head2 PrepareItemrecordDisplay
2306
2307   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2308
2309 Returns a hash with all the fields for Display a given item data in a template
2310
2311 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2312
2313 =cut
2314
2315 sub PrepareItemrecordDisplay {
2316
2317     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2318
2319     my $dbh = C4::Context->dbh;
2320     $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2321     my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
2322
2323     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2324     # a shared data structure. No plugin (including custom ones) should change
2325     # its contents. See also GetMarcStructure.
2326     my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2327
2328     # return nothing if we don't have found an existing framework.
2329     return q{} unless $tagslib;
2330     my $itemrecord;
2331     if ($itemnum) {
2332         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2333     }
2334     my @loop_data;
2335
2336     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2337     my $query = qq{
2338         SELECT authorised_value,lib FROM authorised_values
2339     };
2340     $query .= qq{
2341         LEFT JOIN authorised_values_branches ON ( id = av_id )
2342     } if $branch_limit;
2343     $query .= qq{
2344         WHERE category = ?
2345     };
2346     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2347     $query .= qq{ ORDER BY lib};
2348     my $authorised_values_sth = $dbh->prepare( $query );
2349     foreach my $tag ( sort keys %{$tagslib} ) {
2350         if ( $tag ne '' ) {
2351
2352             # loop through each subfield
2353             my $cntsubf;
2354             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2355                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2356                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2357                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2358                 my %subfield_data;
2359                 $subfield_data{tag}           = $tag;
2360                 $subfield_data{subfield}      = $subfield;
2361                 $subfield_data{countsubfield} = $cntsubf++;
2362                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2363                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2364
2365                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2366                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2367                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2368                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2369                 $subfield_data{hidden}     = "display:none"
2370                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2371                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2372                 my ( $x, $defaultvalue );
2373                 if ($itemrecord) {
2374                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2375                 }
2376                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2377                 if ( !defined $defaultvalue ) {
2378                     $defaultvalue = q||;
2379                 } else {
2380                     $defaultvalue =~ s/"/&quot;/g;
2381                 }
2382
2383                 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
2384
2385                 # search for itemcallnumber if applicable
2386                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2387                     && C4::Context->preference('itemcallnumber') && $itemrecord) {
2388                     foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
2389                         my $CNtag      = substr( $itemcn_pref, 0, 3 );
2390                         next unless my $field = $itemrecord->field($CNtag);
2391                         my $CNsubfields = substr( $itemcn_pref, 3 );
2392                         $defaultvalue = $field->as_string( $CNsubfields, ' ');
2393                         last if $defaultvalue;
2394                     }
2395                 }
2396                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2397                     && $defaultvalues
2398                     && $defaultvalues->{'callnumber'} ) {
2399                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2400                         # if the item record exists, only use default value if the item has no callnumber
2401                         $defaultvalue = $defaultvalues->{callnumber};
2402                     } elsif ( !$itemrecord and $defaultvalues ) {
2403                         # if the item record *doesn't* exists, always use the default value
2404                         $defaultvalue = $defaultvalues->{callnumber};
2405                     }
2406                 }
2407                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2408                     && $defaultvalues
2409                     && $defaultvalues->{'branchcode'} ) {
2410                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2411                         $defaultvalue = $defaultvalues->{branchcode};
2412                     }
2413                 }
2414                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2415                     && $defaultvalues
2416                     && $defaultvalues->{'location'} ) {
2417
2418                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2419                         # if the item record exists, only use default value if the item has no locationr
2420                         $defaultvalue = $defaultvalues->{location};
2421                     } elsif ( !$itemrecord and $defaultvalues ) {
2422                         # if the item record *doesn't* exists, always use the default value
2423                         $defaultvalue = $defaultvalues->{location};
2424                     }
2425                 }
2426                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2427                     my @authorised_values;
2428                     my %authorised_lib;
2429
2430                     # builds list, depending on authorised value...
2431                     #---- branch
2432                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2433                         if (   ( C4::Context->preference("IndependentBranches") )
2434                             && !C4::Context->IsSuperLibrarian() ) {
2435                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2436                             $sth->execute( C4::Context->userenv->{branch} );
2437                             push @authorised_values, ""
2438                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2439                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2440                                 push @authorised_values, $branchcode;
2441                                 $authorised_lib{$branchcode} = $branchname;
2442                             }
2443                         } else {
2444                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2445                             $sth->execute;
2446                             push @authorised_values, ""
2447                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2448                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2449                                 push @authorised_values, $branchcode;
2450                                 $authorised_lib{$branchcode} = $branchname;
2451                             }
2452                         }
2453
2454                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2455                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2456                             $defaultvalue = $defaultvalues->{branchcode};
2457                         }
2458
2459                         #----- itemtypes
2460                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2461                         my $itemtypes = Koha::ItemTypes->search_with_localization;
2462                         push @authorised_values, ""
2463                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2464                         while ( my $itemtype = $itemtypes->next ) {
2465                             push @authorised_values, $itemtype->itemtype;
2466                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2467                         }
2468                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2469                             $defaultvalue = $defaultvalues->{'itemtype'};
2470                         }
2471
2472                         #---- class_sources
2473                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2474                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2475
2476                         my $class_sources = GetClassSources();
2477                         my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
2478
2479                         foreach my $class_source (sort keys %$class_sources) {
2480                             next unless $class_sources->{$class_source}->{'used'} or
2481                                         ($class_source eq $default_source);
2482                             push @authorised_values, $class_source;
2483                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2484                         }
2485
2486                         $defaultvalue = $default_source;
2487
2488                         #---- "true" authorised value
2489                     } else {
2490                         $authorised_values_sth->execute(
2491                             $tagslib->{$tag}->{$subfield}->{authorised_value},
2492                             $branch_limit ? $branch_limit : ()
2493                         );
2494                         push @authorised_values, ""
2495                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2496                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2497                             push @authorised_values, $value;
2498                             $authorised_lib{$value} = $lib;
2499                         }
2500                     }
2501                     $subfield_data{marc_value} = {
2502                         type    => 'select',
2503                         values  => \@authorised_values,
2504                         default => $defaultvalue // q{},
2505                         labels  => \%authorised_lib,
2506                     };
2507                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2508                 # it is a plugin
2509                     require Koha::FrameworkPlugin;
2510                     my $plugin = Koha::FrameworkPlugin->new({
2511                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
2512                         item_style => 1,
2513                     });
2514                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2515                     $plugin->build( $pars );
2516                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2517                         $defaultvalue = $field->subfield($subfield) || q{};
2518                     }
2519                     if( !$plugin->errstr ) {
2520                         #TODO Move html to template; see report 12176/13397
2521                         my $tab= $plugin->noclick? '-1': '';
2522                         my $class= $plugin->noclick? ' disabled': '';
2523                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
2524                         $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
2525                     } else {
2526                         warn $plugin->errstr;
2527                         $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
2528                     }
2529                 }
2530                 elsif ( $tag eq '' ) {       # it's an hidden field
2531                     $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2532                 }
2533                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
2534                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2535                 }
2536                 elsif ( length($defaultvalue) > 100
2537                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2538                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
2539                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
2540                                   500 <= $tag && $tag < 600                     )
2541                           ) {
2542                     # oversize field (textarea)
2543                     $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
2544                 } else {
2545                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2546                 }
2547                 push( @loop_data, \%subfield_data );
2548             }
2549         }
2550     }
2551     my $itemnumber;
2552     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2553         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2554     }
2555     return {
2556         'itemtagfield'    => $itemtagfield,
2557         'itemtagsubfield' => $itemtagsubfield,
2558         'itemnumber'      => $itemnumber,
2559         'iteminformation' => \@loop_data
2560     };
2561 }
2562
2563 sub ToggleNewStatus {
2564     my ( $params ) = @_;
2565     my @rules = @{ $params->{rules} };
2566     my $report_only = $params->{report_only};
2567
2568     my $dbh = C4::Context->dbh;
2569     my @errors;
2570     my @item_columns = map { "items.$_" } Koha::Items->columns;
2571     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2572     my $report;
2573     for my $rule ( @rules ) {
2574         my $age = $rule->{age};
2575         my $conditions = $rule->{conditions};
2576         my $substitutions = $rule->{substitutions};
2577         foreach ( @$substitutions ) {
2578             ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2579         }
2580         my @params;
2581
2582         my $query = q|
2583             SELECT items.*
2584             FROM items
2585             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2586             WHERE 1
2587         |;
2588         for my $condition ( @$conditions ) {
2589             if (
2590                  grep { $_ eq $condition->{field} } @item_columns
2591               or grep { $_ eq $condition->{field} } @biblioitem_columns
2592             ) {
2593                 if ( $condition->{value} =~ /\|/ ) {
2594                     my @values = split /\|/, $condition->{value};
2595                     $query .= qq| AND $condition->{field} IN (|
2596                         . join( ',', ('?') x scalar @values )
2597                         . q|)|;
2598                     push @params, @values;
2599                 } else {
2600                     $query .= qq| AND $condition->{field} = ?|;
2601                     push @params, $condition->{value};
2602                 }
2603             }
2604         }
2605         if ( defined $age ) {
2606             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2607             push @params, $age;
2608         }
2609         my $sth = $dbh->prepare($query);
2610         $sth->execute( @params );
2611         while ( my $values = $sth->fetchrow_hashref ) {
2612             my $biblionumber = $values->{biblionumber};
2613             my $itemnumber = $values->{itemnumber};
2614             for my $substitution ( @$substitutions ) {
2615                 next unless $substitution->{field};
2616                 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2617                 C4::Items::ModItem( { $substitution->{item_field} => $substitution->{value} }, $biblionumber, $itemnumber )
2618                     unless $report_only;
2619                 push @{ $report->{$itemnumber} }, $substitution;
2620             }
2621         }
2622     }
2623
2624     return $report;
2625 }
2626
2627 =head2 _after_item_action_hooks
2628
2629 Helper method that takes care of calling all plugin hooks
2630
2631 =cut
2632
2633 sub _after_item_action_hooks {
2634     my ( $args ) = @_;
2635
2636     my $item_id = $args->{item_id};
2637     my $action  = $args->{action};
2638
2639     if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
2640
2641         my @plugins = Koha::Plugins->new->GetPlugins({
2642             method => 'after_item_action',
2643         });
2644
2645         if (@plugins) {
2646
2647             my $item = Koha::Items->find( $item_id );
2648
2649             foreach my $plugin ( @plugins ) {
2650                 try {
2651                     $plugin->after_item_action({ action => $action, item => $item, item_id => $item_id });
2652                 }
2653                 catch {
2654                     warn "$_";
2655                 };
2656             }
2657         }
2658     }
2659 }
2660
2661 1;