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