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