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