Bug 15505: Mark Hold Items 'On hold' instead of 'Available'
[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 .= ",IF(tmp_holdsqueue.itemnumber,1,0) AS pending_hold" if !C4::Context->preference('AllowItemsOnHoldCheckout');
967     $query .= "
968      FROM items
969      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
970      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
971      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
972      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
973      LEFT JOIN issues USING (itemnumber)
974      LEFT JOIN borrowers USING (borrowernumber)
975      LEFT JOIN serialitems USING (itemnumber)
976      LEFT JOIN serial USING (serialid)
977      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
978      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
979     $query .= "
980     LEFT JOIN tmp_holdsqueue USING (itemnumber)" if !C4::Context->preference('AllowItemsOnHoldCheckout');
981     $query .= q|
982     LEFT JOIN localization ON itemtypes.itemtype = localization.code
983         AND localization.entity = 'itemtypes'
984         AND localization.lang = ?
985     |;
986
987     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
988     my $sth = $dbh->prepare($query);
989     $sth->execute($language, $biblionumber);
990     my $i = 0;
991     my @results;
992     my $serial;
993
994     my $userenv = C4::Context->userenv;
995     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
996     while ( my $data = $sth->fetchrow_hashref ) {
997         if ( $data->{borrowernumber} && $want_not_same_branch) {
998             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
999         }
1000
1001         $serial ||= $data->{'serial'};
1002
1003         my $descriptions;
1004         # get notforloan complete status if applicable
1005         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
1006         $data->{notforloanvalue}     = $descriptions->{lib} // '';
1007         $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
1008
1009         # get restricted status and description if applicable
1010         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
1011         $data->{restrictedvalue}     = $descriptions->{lib} // '';
1012         $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
1013
1014         # my stack procedures
1015         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
1016         $data->{stack}          = $descriptions->{lib} // '';
1017
1018         # Find the last 3 people who borrowed this item.
1019         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1020                                     WHERE itemnumber = ?
1021                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1022                                     ORDER BY returndate DESC
1023                                     LIMIT 3");
1024         $sth2->execute($data->{'itemnumber'});
1025         my $ii = 0;
1026         while (my $data2 = $sth2->fetchrow_hashref()) {
1027             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1028             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1029             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1030             $ii++;
1031         }
1032
1033         $results[$i] = $data;
1034         $i++;
1035     }
1036
1037     return $serial
1038         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1039         : @results;
1040 }
1041
1042 =head2 GetItemsLocationInfo
1043
1044   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1045
1046 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1047
1048 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1049
1050 =over 2
1051
1052 =item C<$data-E<gt>{homebranch}>
1053
1054 Branch Name of the item's homebranch
1055
1056 =item C<$data-E<gt>{holdingbranch}>
1057
1058 Branch Name of the item's holdingbranch
1059
1060 =item C<$data-E<gt>{location}>
1061
1062 Item's shelving location code
1063
1064 =item C<$data-E<gt>{location_intranet}>
1065
1066 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1067
1068 =item C<$data-E<gt>{location_opac}>
1069
1070 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
1071 description is set.
1072
1073 =item C<$data-E<gt>{itemcallnumber}>
1074
1075 Item's itemcallnumber
1076
1077 =item C<$data-E<gt>{cn_sort}>
1078
1079 Item's call number normalized for sorting
1080
1081 =back
1082   
1083 =cut
1084
1085 sub GetItemsLocationInfo {
1086         my $biblionumber = shift;
1087         my @results;
1088
1089         my $dbh = C4::Context->dbh;
1090         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
1091                             location, itemcallnumber, cn_sort
1092                      FROM items, branches as a, branches as b
1093                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
1094                      AND biblionumber = ?
1095                      ORDER BY cn_sort ASC";
1096         my $sth = $dbh->prepare($query);
1097         $sth->execute($biblionumber);
1098
1099         while ( my $data = $sth->fetchrow_hashref ) {
1100              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1101              $av = $av->count ? $av->next : undef;
1102              $data->{location_intranet} = $av ? $av->lib : '';
1103              $data->{location_opac}     = $av ? $av->opac_description : '';
1104              push @results, $data;
1105         }
1106         return @results;
1107 }
1108
1109 =head2 GetHostItemsInfo
1110
1111     $hostiteminfo = GetHostItemsInfo($hostfield);
1112     Returns the iteminfo for items linked to records via a host field
1113
1114 =cut
1115
1116 sub GetHostItemsInfo {
1117     my ($record) = @_;
1118     my @returnitemsInfo;
1119
1120     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1121         return @returnitemsInfo;
1122     }
1123
1124     my @fields;
1125     if( C4::Context->preference('marcflavour') eq 'MARC21' ||
1126       C4::Context->preference('marcflavour') eq 'NORMARC') {
1127         @fields = $record->field('773');
1128     } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
1129         @fields = $record->field('461');
1130     }
1131
1132     foreach my $hostfield ( @fields ) {
1133         my $hostbiblionumber = $hostfield->subfield("0");
1134         my $linkeditemnumber = $hostfield->subfield("9");
1135         my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1136         foreach my $hostitemInfo (@hostitemInfos) {
1137             if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
1138                 push @returnitemsInfo, $hostitemInfo;
1139                 last;
1140             }
1141         }
1142     }
1143     return @returnitemsInfo;
1144 }
1145
1146 =head2 get_hostitemnumbers_of
1147
1148   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1149
1150 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1151
1152 Return a reference on a hash where key is a biblionumber and values are
1153 references on array of itemnumbers.
1154
1155 =cut
1156
1157
1158 sub get_hostitemnumbers_of {
1159     my ($biblionumber) = @_;
1160
1161     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1162         return ();
1163     }
1164
1165     my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
1166     return unless $marcrecord;
1167
1168     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1169
1170     my $marcflavor = C4::Context->preference('marcflavour');
1171     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1172         $tag      = '773';
1173         $biblio_s = '0';
1174         $item_s   = '9';
1175     }
1176     elsif ( $marcflavor eq 'UNIMARC' ) {
1177         $tag      = '461';
1178         $biblio_s = '0';
1179         $item_s   = '9';
1180     }
1181
1182     foreach my $hostfield ( $marcrecord->field($tag) ) {
1183         my $hostbiblionumber = $hostfield->subfield($biblio_s);
1184         next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1185         my $linkeditemnumber = $hostfield->subfield($item_s);
1186         if ( ! $linkeditemnumber ) {
1187             warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1188             next;
1189         }
1190         my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1191         push @returnhostitemnumbers, $linkeditemnumber
1192           if $is_from_biblio;
1193     }
1194
1195     return @returnhostitemnumbers;
1196 }
1197
1198 =head2 GetHiddenItemnumbers
1199
1200     my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
1201
1202 Given a list of items it checks which should be hidden from the OPAC given
1203 the current configuration. Returns a list of itemnumbers corresponding to
1204 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
1205 to be excluded
1206
1207 =cut
1208
1209 sub GetHiddenItemnumbers {
1210     my $params = shift;
1211     my $items = $params->{items};
1212     if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1213         foreach my $except (split(/\|/, $exceptions)){
1214             if ($params->{'borcat'} eq $except){
1215                 return; # we don't hide anything for this borrower category
1216             }
1217         }
1218     }
1219     my @resultitems;
1220
1221     my $yaml = C4::Context->preference('OpacHiddenItems');
1222     return () if (! $yaml =~ /\S/ );
1223     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1224     my $hidingrules;
1225     eval {
1226         $hidingrules = YAML::Load($yaml);
1227     };
1228     if ($@) {
1229         warn "Unable to parse OpacHiddenItems syspref : $@";
1230         return ();
1231     }
1232     my $dbh = C4::Context->dbh;
1233
1234     # For each item
1235     foreach my $item (@$items) {
1236
1237         # We check each rule
1238         foreach my $field (keys %$hidingrules) {
1239             my $val;
1240             if (exists $item->{$field}) {
1241                 $val = $item->{$field};
1242             }
1243             else {
1244                 my $query = "SELECT $field from items where itemnumber = ?";
1245                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1246             }
1247             $val = '' unless defined $val;
1248
1249             # If the results matches the values in the yaml file
1250             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1251
1252                 # We add the itemnumber to the list
1253                 push @resultitems, $item->{'itemnumber'};
1254
1255                 # If at least one rule matched for an item, no need to test the others
1256                 last;
1257             }
1258         }
1259     }
1260     return @resultitems;
1261 }
1262
1263 =head1 LIMITED USE FUNCTIONS
1264
1265 The following functions, while part of the public API,
1266 are not exported.  This is generally because they are
1267 meant to be used by only one script for a specific
1268 purpose, and should not be used in any other context
1269 without careful thought.
1270
1271 =cut
1272
1273 =head2 GetMarcItem
1274
1275   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1276
1277 Returns MARC::Record of the item passed in parameter.
1278 This function is meant for use only in C<cataloguing/additem.pl>,
1279 where it is needed to support that script's MARC-like
1280 editor.
1281
1282 =cut
1283
1284 sub GetMarcItem {
1285     my ( $biblionumber, $itemnumber ) = @_;
1286
1287     # GetMarcItem has been revised so that it does the following:
1288     #  1. Gets the item information from the items table.
1289     #  2. Converts it to a MARC field for storage in the bib record.
1290     #
1291     # The previous behavior was:
1292     #  1. Get the bib record.
1293     #  2. Return the MARC tag corresponding to the item record.
1294     #
1295     # The difference is that one treats the items row as authoritative,
1296     # while the other treats the MARC representation as authoritative
1297     # under certain circumstances.
1298
1299     my $item = Koha::Items->find($itemnumber) or return;
1300
1301     # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1302     # Also, don't emit a subfield if the underlying field is blank.
1303
1304     return Item2Marc($item->unblessed, $biblionumber);
1305
1306 }
1307 sub Item2Marc {
1308         my ($itemrecord,$biblionumber)=@_;
1309     my $mungeditem = { 
1310         map {  
1311             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1312         } keys %{ $itemrecord } 
1313     };
1314     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1315     my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1316     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1317         "items.itemnumber", $framework,
1318     );
1319
1320     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1321     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1322                 foreach my $field ($itemmarc->field($itemtag)){
1323             $field->add_subfields(@$unlinked_item_subfields);
1324         }
1325     }
1326         return $itemmarc;
1327 }
1328
1329 =head1 PRIVATE FUNCTIONS AND VARIABLES
1330
1331 The following functions are not meant to be called
1332 directly, but are documented in order to explain
1333 the inner workings of C<C4::Items>.
1334
1335 =cut
1336
1337 =head2 %derived_columns
1338
1339 This hash keeps track of item columns that
1340 are strictly derived from other columns in
1341 the item record and are not meant to be set
1342 independently.
1343
1344 Each key in the hash should be the name of a
1345 column (as named by TransformMarcToKoha).  Each
1346 value should be hashref whose keys are the
1347 columns on which the derived column depends.  The
1348 hashref should also contain a 'BUILDER' key
1349 that is a reference to a sub that calculates
1350 the derived value.
1351
1352 =cut
1353
1354 my %derived_columns = (
1355     'items.cn_sort' => {
1356         'itemcallnumber' => 1,
1357         'items.cn_source' => 1,
1358         'BUILDER' => \&_calc_items_cn_sort,
1359     }
1360 );
1361
1362 =head2 _set_derived_columns_for_add 
1363
1364   _set_derived_column_for_add($item);
1365
1366 Given an item hash representing a new item to be added,
1367 calculate any derived columns.  Currently the only
1368 such column is C<items.cn_sort>.
1369
1370 =cut
1371
1372 sub _set_derived_columns_for_add {
1373     my $item = shift;
1374
1375     foreach my $column (keys %derived_columns) {
1376         my $builder = $derived_columns{$column}->{'BUILDER'};
1377         my $source_values = {};
1378         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1379             next if $source_column eq 'BUILDER';
1380             $source_values->{$source_column} = $item->{$source_column};
1381         }
1382         $builder->($item, $source_values);
1383     }
1384 }
1385
1386 =head2 _set_derived_columns_for_mod 
1387
1388   _set_derived_column_for_mod($item);
1389
1390 Given an item hash representing a new item to be modified.
1391 calculate any derived columns.  Currently the only
1392 such column is C<items.cn_sort>.
1393
1394 This routine differs from C<_set_derived_columns_for_add>
1395 in that it needs to handle partial item records.  In other
1396 words, the caller of C<ModItem> may have supplied only one
1397 or two columns to be changed, so this function needs to
1398 determine whether any of the columns to be changed affect
1399 any of the derived columns.  Also, if a derived column
1400 depends on more than one column, but the caller is not
1401 changing all of then, this routine retrieves the unchanged
1402 values from the database in order to ensure a correct
1403 calculation.
1404
1405 =cut
1406
1407 sub _set_derived_columns_for_mod {
1408     my $item = shift;
1409
1410     foreach my $column (keys %derived_columns) {
1411         my $builder = $derived_columns{$column}->{'BUILDER'};
1412         my $source_values = {};
1413         my %missing_sources = ();
1414         my $must_recalc = 0;
1415         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1416             next if $source_column eq 'BUILDER';
1417             if (exists $item->{$source_column}) {
1418                 $must_recalc = 1;
1419                 $source_values->{$source_column} = $item->{$source_column};
1420             } else {
1421                 $missing_sources{$source_column} = 1;
1422             }
1423         }
1424         if ($must_recalc) {
1425             foreach my $source_column (keys %missing_sources) {
1426                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1427             }
1428             $builder->($item, $source_values);
1429         }
1430     }
1431 }
1432
1433 =head2 _do_column_fixes_for_mod
1434
1435   _do_column_fixes_for_mod($item);
1436
1437 Given an item hashref containing one or more
1438 columns to modify, fix up certain values.
1439 Specifically, set to 0 any passed value
1440 of C<notforloan>, C<damaged>, C<itemlost>, or
1441 C<withdrawn> that is either undefined or
1442 contains the empty string.
1443
1444 =cut
1445
1446 sub _do_column_fixes_for_mod {
1447     my $item = shift;
1448
1449     if (exists $item->{'notforloan'} and
1450         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1451         $item->{'notforloan'} = 0;
1452     }
1453     if (exists $item->{'damaged'} and
1454         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1455         $item->{'damaged'} = 0;
1456     }
1457     if (exists $item->{'itemlost'} and
1458         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1459         $item->{'itemlost'} = 0;
1460     }
1461     if (exists $item->{'withdrawn'} and
1462         (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1463         $item->{'withdrawn'} = 0;
1464     }
1465     if (exists $item->{location}
1466         and $item->{location} ne 'CART'
1467         and $item->{location} ne 'PROC'
1468         and not $item->{permanent_location}
1469     ) {
1470         $item->{'permanent_location'} = $item->{'location'};
1471     }
1472     if (exists $item->{'timestamp'}) {
1473         delete $item->{'timestamp'};
1474     }
1475 }
1476
1477 =head2 _get_single_item_column
1478
1479   _get_single_item_column($column, $itemnumber);
1480
1481 Retrieves the value of a single column from an C<items>
1482 row specified by C<$itemnumber>.
1483
1484 =cut
1485
1486 sub _get_single_item_column {
1487     my $column = shift;
1488     my $itemnumber = shift;
1489     
1490     my $dbh = C4::Context->dbh;
1491     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1492     $sth->execute($itemnumber);
1493     my ($value) = $sth->fetchrow();
1494     return $value; 
1495 }
1496
1497 =head2 _calc_items_cn_sort
1498
1499   _calc_items_cn_sort($item, $source_values);
1500
1501 Helper routine to calculate C<items.cn_sort>.
1502
1503 =cut
1504
1505 sub _calc_items_cn_sort {
1506     my $item = shift;
1507     my $source_values = shift;
1508
1509     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1510 }
1511
1512 =head2 _set_defaults_for_add 
1513
1514   _set_defaults_for_add($item_hash);
1515
1516 Given an item hash representing an item to be added, set
1517 correct default values for columns whose default value
1518 is not handled by the DBMS.  This includes the following
1519 columns:
1520
1521 =over 2
1522
1523 =item * 
1524
1525 C<items.dateaccessioned>
1526
1527 =item *
1528
1529 C<items.notforloan>
1530
1531 =item *
1532
1533 C<items.damaged>
1534
1535 =item *
1536
1537 C<items.itemlost>
1538
1539 =item *
1540
1541 C<items.withdrawn>
1542
1543 =back
1544
1545 =cut
1546
1547 sub _set_defaults_for_add {
1548     my $item = shift;
1549     $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1550     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1551 }
1552
1553 =head2 _koha_new_item
1554
1555   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1556
1557 Perform the actual insert into the C<items> table.
1558
1559 =cut
1560
1561 sub _koha_new_item {
1562     my ( $item, $barcode ) = @_;
1563     my $dbh=C4::Context->dbh;  
1564     my $error;
1565     $item->{permanent_location} //= $item->{location};
1566     _mod_item_dates( $item );
1567     my $query =
1568            "INSERT INTO items SET
1569             biblionumber        = ?,
1570             biblioitemnumber    = ?,
1571             barcode             = ?,
1572             dateaccessioned     = ?,
1573             booksellerid        = ?,
1574             homebranch          = ?,
1575             price               = ?,
1576             replacementprice    = ?,
1577             replacementpricedate = ?,
1578             datelastborrowed    = ?,
1579             datelastseen        = ?,
1580             stack               = ?,
1581             notforloan          = ?,
1582             damaged             = ?,
1583             itemlost            = ?,
1584             withdrawn           = ?,
1585             itemcallnumber      = ?,
1586             coded_location_qualifier = ?,
1587             restricted          = ?,
1588             itemnotes           = ?,
1589             itemnotes_nonpublic = ?,
1590             holdingbranch       = ?,
1591             paidfor             = ?,
1592             location            = ?,
1593             permanent_location  = ?,
1594             onloan              = ?,
1595             issues              = ?,
1596             renewals            = ?,
1597             reserves            = ?,
1598             cn_source           = ?,
1599             cn_sort             = ?,
1600             ccode               = ?,
1601             itype               = ?,
1602             materials           = ?,
1603             uri                 = ?,
1604             enumchron           = ?,
1605             more_subfields_xml  = ?,
1606             copynumber          = ?,
1607             stocknumber         = ?,
1608             new_status          = ?
1609           ";
1610     my $sth = $dbh->prepare($query);
1611     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1612    $sth->execute(
1613             $item->{'biblionumber'},
1614             $item->{'biblioitemnumber'},
1615             $barcode,
1616             $item->{'dateaccessioned'},
1617             $item->{'booksellerid'},
1618             $item->{'homebranch'},
1619             $item->{'price'},
1620             $item->{'replacementprice'},
1621             $item->{'replacementpricedate'} || $today,
1622             $item->{datelastborrowed},
1623             $item->{datelastseen} || $today,
1624             $item->{stack},
1625             $item->{'notforloan'},
1626             $item->{'damaged'},
1627             $item->{'itemlost'},
1628             $item->{'withdrawn'},
1629             $item->{'itemcallnumber'},
1630             $item->{'coded_location_qualifier'},
1631             $item->{'restricted'},
1632             $item->{'itemnotes'},
1633             $item->{'itemnotes_nonpublic'},
1634             $item->{'holdingbranch'},
1635             $item->{'paidfor'},
1636             $item->{'location'},
1637             $item->{'permanent_location'},
1638             $item->{'onloan'},
1639             $item->{'issues'},
1640             $item->{'renewals'},
1641             $item->{'reserves'},
1642             $item->{'items.cn_source'},
1643             $item->{'items.cn_sort'},
1644             $item->{'ccode'},
1645             $item->{'itype'},
1646             $item->{'materials'},
1647             $item->{'uri'},
1648             $item->{'enumchron'},
1649             $item->{'more_subfields_xml'},
1650             $item->{'copynumber'},
1651             $item->{'stocknumber'},
1652             $item->{'new_status'},
1653     );
1654
1655     my $itemnumber;
1656     if ( defined $sth->errstr ) {
1657         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1658     }
1659     else {
1660         $itemnumber = $dbh->{'mysql_insertid'};
1661     }
1662
1663     return ( $itemnumber, $error );
1664 }
1665
1666 =head2 MoveItemFromBiblio
1667
1668   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1669
1670 Moves an item from a biblio to another
1671
1672 Returns undef if the move failed or the biblionumber of the destination record otherwise
1673
1674 =cut
1675
1676 sub MoveItemFromBiblio {
1677     my ($itemnumber, $frombiblio, $tobiblio) = @_;
1678     my $dbh = C4::Context->dbh;
1679     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1680         SELECT biblioitemnumber
1681         FROM biblioitems
1682         WHERE biblionumber = ?
1683     |, undef, $tobiblio );
1684     my $return = $dbh->do(q|
1685         UPDATE items
1686         SET biblioitemnumber = ?,
1687             biblionumber = ?
1688         WHERE itemnumber = ?
1689             AND biblionumber = ?
1690     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1691     if ($return == 1) {
1692         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1693         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1694             # Checking if the item we want to move is in an order 
1695         require C4::Acquisition;
1696         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1697             if ($order) {
1698                     # Replacing the biblionumber within the order if necessary
1699                     $order->{'biblionumber'} = $tobiblio;
1700                 C4::Acquisition::ModOrder($order);
1701             }
1702
1703         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1704         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1705             $dbh->do( qq|
1706                 UPDATE $table_name
1707                 SET biblionumber = ?
1708                 WHERE itemnumber = ?
1709             |, undef, $tobiblio, $itemnumber );
1710         }
1711         return $tobiblio;
1712         }
1713     return;
1714 }
1715
1716 =head2 ItemSafeToDelete
1717
1718    ItemSafeToDelete( $biblionumber, $itemnumber);
1719
1720 Exported function (core API) for checking whether an item record is safe to delete.
1721
1722 returns 1 if the item is safe to delete,
1723
1724 "book_on_loan" if the item is checked out,
1725
1726 "not_same_branch" if the item is blocked by independent branches,
1727
1728 "book_reserved" if the there are holds aganst the item, or
1729
1730 "linked_analytics" if the item has linked analytic records.
1731
1732 =cut
1733
1734 sub ItemSafeToDelete {
1735     my ( $biblionumber, $itemnumber ) = @_;
1736     my $status;
1737     my $dbh = C4::Context->dbh;
1738
1739     my $error;
1740
1741     my $countanalytics = GetAnalyticsCount($itemnumber);
1742
1743     my $item = Koha::Items->find($itemnumber) or return;
1744
1745     if ($item->checkout) {
1746         $status = "book_on_loan";
1747     }
1748     elsif ( defined C4::Context->userenv
1749         and !C4::Context->IsSuperLibrarian()
1750         and C4::Context->preference("IndependentBranches")
1751         and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1752     {
1753         $status = "not_same_branch";
1754     }
1755     else {
1756         # check it doesn't have a waiting reserve
1757         my $sth = $dbh->prepare(
1758             q{
1759             SELECT COUNT(*) FROM reserves
1760             WHERE (found = 'W' OR found = 'T')
1761             AND itemnumber = ?
1762         }
1763         );
1764         $sth->execute($itemnumber);
1765         my ($reserve) = $sth->fetchrow;
1766         if ($reserve) {
1767             $status = "book_reserved";
1768         }
1769         elsif ( $countanalytics > 0 ) {
1770             $status = "linked_analytics";
1771         }
1772         else {
1773             $status = 1;
1774         }
1775     }
1776     return $status;
1777 }
1778
1779 =head2 DelItemCheck
1780
1781    DelItemCheck( $biblionumber, $itemnumber);
1782
1783 Exported function (core API) for deleting an item record in Koha if there no current issue.
1784
1785 DelItemCheck wraps ItemSafeToDelete around DelItem.
1786
1787 =cut
1788
1789 sub DelItemCheck {
1790     my ( $biblionumber, $itemnumber ) = @_;
1791     my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1792
1793     if ( $status == 1 ) {
1794         DelItem(
1795             {
1796                 biblionumber => $biblionumber,
1797                 itemnumber   => $itemnumber
1798             }
1799         );
1800     }
1801     return $status;
1802 }
1803
1804 =head2 _koha_modify_item
1805
1806   my ($itemnumber,$error) =_koha_modify_item( $item );
1807
1808 Perform the actual update of the C<items> row.  Note that this
1809 routine accepts a hashref specifying the columns to update.
1810
1811 =cut
1812
1813 sub _koha_modify_item {
1814     my ( $item ) = @_;
1815     my $dbh=C4::Context->dbh;  
1816     my $error;
1817
1818     my $query = "UPDATE items SET ";
1819     my @bind;
1820     _mod_item_dates( $item );
1821     for my $key ( keys %$item ) {
1822         next if ( $key eq 'itemnumber' );
1823         $query.="$key=?,";
1824         push @bind, $item->{$key};
1825     }
1826     $query =~ s/,$//;
1827     $query .= " WHERE itemnumber=?";
1828     push @bind, $item->{'itemnumber'};
1829     my $sth = $dbh->prepare($query);
1830     $sth->execute(@bind);
1831     if ( $sth->err ) {
1832         $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
1833         warn $error;
1834     }
1835     return ($item->{'itemnumber'},$error);
1836 }
1837
1838 sub _mod_item_dates { # date formatting for date fields in item hash
1839     my ( $item ) = @_;
1840     return if !$item || ref($item) ne 'HASH';
1841
1842     my @keys = grep
1843         { $_ =~ /^onloan$|^date|date$|datetime$/ }
1844         keys %$item;
1845     # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1846     # NOTE: We do not (yet) have items fields ending with datetime
1847     # Fields with _on$ have been handled already
1848
1849     foreach my $key ( @keys ) {
1850         next if !defined $item->{$key}; # skip undefs
1851         my $dt = eval { dt_from_string( $item->{$key} ) };
1852             # eval: dt_from_string will die on us if we pass illegal dates
1853
1854         my $newstr;
1855         if( defined $dt  && ref($dt) eq 'DateTime' ) {
1856             if( $key =~ /datetime/ ) {
1857                 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1858             } else {
1859                 $newstr = DateTime::Format::MySQL->format_date($dt);
1860             }
1861         }
1862         $item->{$key} = $newstr; # might be undef to clear garbage
1863     }
1864 }
1865
1866 =head2 _koha_delete_item
1867
1868   _koha_delete_item( $itemnum );
1869
1870 Internal function to delete an item record from the koha tables
1871
1872 =cut
1873
1874 sub _koha_delete_item {
1875     my ( $itemnum ) = @_;
1876
1877     my $dbh = C4::Context->dbh;
1878     # save the deleted item to deleteditems table
1879     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1880     $sth->execute($itemnum);
1881     my $data = $sth->fetchrow_hashref();
1882
1883     # There is no item to delete
1884     return 0 unless $data;
1885
1886     my $query = "INSERT INTO deleteditems SET ";
1887     my @bind  = ();
1888     foreach my $key ( keys %$data ) {
1889         next if ( $key eq 'timestamp' ); # timestamp will be set by db
1890         $query .= "$key = ?,";
1891         push( @bind, $data->{$key} );
1892     }
1893     $query =~ s/\,$//;
1894     $sth = $dbh->prepare($query);
1895     $sth->execute(@bind);
1896
1897     # delete from items table
1898     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1899     my $deleted = $sth->execute($itemnum);
1900     return ( $deleted == 1 ) ? 1 : 0;
1901 }
1902
1903 =head2 _marc_from_item_hash
1904
1905   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1906
1907 Given an item hash representing a complete item record,
1908 create a C<MARC::Record> object containing an embedded
1909 tag representing that item.
1910
1911 The third, optional parameter C<$unlinked_item_subfields> is
1912 an arrayref of subfields (not mapped to C<items> fields per the
1913 framework) to be added to the MARC representation
1914 of the item.
1915
1916 =cut
1917
1918 sub _marc_from_item_hash {
1919     my $item = shift;
1920     my $frameworkcode = shift;
1921     my $unlinked_item_subfields;
1922     if (@_) {
1923         $unlinked_item_subfields = shift;
1924     }
1925    
1926     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1927     # Also, don't emit a subfield if the underlying field is blank.
1928     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1929                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1930                                 : ()  } keys %{ $item } }; 
1931
1932     my $item_marc = MARC::Record->new();
1933     foreach my $item_field ( keys %{$mungeditem} ) {
1934         my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field, $frameworkcode );
1935         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
1936         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1937         foreach my $value (@values){
1938             if ( my $field = $item_marc->field($tag) ) {
1939                     $field->add_subfields( $subfield => $value );
1940             } else {
1941                 my $add_subfields = [];
1942                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1943                     $add_subfields = $unlinked_item_subfields;
1944             }
1945             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1946             }
1947         }
1948     }
1949
1950     return $item_marc;
1951 }
1952
1953 =head2 _repack_item_errors
1954
1955 Add an error message hash generated by C<CheckItemPreSave>
1956 to a list of errors.
1957
1958 =cut
1959
1960 sub _repack_item_errors {
1961     my $item_sequence_num = shift;
1962     my $item_ref = shift;
1963     my $error_ref = shift;
1964
1965     my @repacked_errors = ();
1966
1967     foreach my $error_code (sort keys %{ $error_ref }) {
1968         my $repacked_error = {};
1969         $repacked_error->{'item_sequence'} = $item_sequence_num;
1970         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1971         $repacked_error->{'error_code'} = $error_code;
1972         $repacked_error->{'error_information'} = $error_ref->{$error_code};
1973         push @repacked_errors, $repacked_error;
1974     } 
1975
1976     return @repacked_errors;
1977 }
1978
1979 =head2 _get_unlinked_item_subfields
1980
1981   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1982
1983 =cut
1984
1985 sub _get_unlinked_item_subfields {
1986     my $original_item_marc = shift;
1987     my $frameworkcode = shift;
1988
1989     my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1990
1991     # assume that this record has only one field, and that that
1992     # field contains only the item information
1993     my $subfields = [];
1994     my @fields = $original_item_marc->fields();
1995     if ($#fields > -1) {
1996         my $field = $fields[0];
1997             my $tag = $field->tag();
1998         foreach my $subfield ($field->subfields()) {
1999             if (defined $subfield->[1] and
2000                 $subfield->[1] ne '' and
2001                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2002                 push @$subfields, $subfield->[0] => $subfield->[1];
2003             }
2004         }
2005     }
2006     return $subfields;
2007 }
2008
2009 =head2 _get_unlinked_subfields_xml
2010
2011   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2012
2013 =cut
2014
2015 sub _get_unlinked_subfields_xml {
2016     my $unlinked_item_subfields = shift;
2017
2018     my $xml;
2019     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2020         my $marc = MARC::Record->new();
2021         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2022         # used in the framework
2023         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2024         $marc->encoding("UTF-8");    
2025         $xml = $marc->as_xml("USMARC");
2026     }
2027
2028     return $xml;
2029 }
2030
2031 =head2 _parse_unlinked_item_subfields_from_xml
2032
2033   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2034
2035 =cut
2036
2037 sub  _parse_unlinked_item_subfields_from_xml {
2038     my $xml = shift;
2039     require C4::Charset;
2040     return unless defined $xml and $xml ne "";
2041     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2042     my $unlinked_subfields = [];
2043     my @fields = $marc->fields();
2044     if ($#fields > -1) {
2045         foreach my $subfield ($fields[0]->subfields()) {
2046             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2047         }
2048     }
2049     return $unlinked_subfields;
2050 }
2051
2052 =head2 GetAnalyticsCount
2053
2054   $count= &GetAnalyticsCount($itemnumber)
2055
2056 counts Usage of itemnumber in Analytical bibliorecords. 
2057
2058 =cut
2059
2060 sub GetAnalyticsCount {
2061     my ($itemnumber) = @_;
2062
2063     ### ZOOM search here
2064     my $query;
2065     $query= "hi=".$itemnumber;
2066     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2067     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2068     return ($result);
2069 }
2070
2071 =head2 SearchItemsByField
2072
2073     my $items = SearchItemsByField($field, $value);
2074
2075 SearchItemsByField will search for items on a specific given field.
2076 For instance you can search all items with a specific stocknumber like this:
2077
2078     my $items = SearchItemsByField('stocknumber', $stocknumber);
2079
2080 =cut
2081
2082 sub SearchItemsByField {
2083     my ($field, $value) = @_;
2084
2085     my $filters = {
2086         field => $field,
2087         query => $value,
2088     };
2089
2090     my ($results) = SearchItems($filters);
2091     return $results;
2092 }
2093
2094 sub _SearchItems_build_where_fragment {
2095     my ($filter) = @_;
2096
2097     my $dbh = C4::Context->dbh;
2098
2099     my $where_fragment;
2100     if (exists($filter->{conjunction})) {
2101         my (@where_strs, @where_args);
2102         foreach my $f (@{ $filter->{filters} }) {
2103             my $fragment = _SearchItems_build_where_fragment($f);
2104             if ($fragment) {
2105                 push @where_strs, $fragment->{str};
2106                 push @where_args, @{ $fragment->{args} };
2107             }
2108         }
2109         my $where_str = '';
2110         if (@where_strs) {
2111             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2112             $where_fragment = {
2113                 str => $where_str,
2114                 args => \@where_args,
2115             };
2116         }
2117     } else {
2118         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2119         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2120         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2121         my @operators = qw(= != > < >= <= like);
2122         my $field = $filter->{field};
2123         if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2124             my $op = $filter->{operator};
2125             my $query = $filter->{query};
2126
2127             if (!$op or (0 == grep /^$op$/, @operators)) {
2128                 $op = '='; # default operator
2129             }
2130
2131             my $column;
2132             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2133                 my $marcfield = $1;
2134                 my $marcsubfield = $2;
2135                 my ($kohafield) = $dbh->selectrow_array(q|
2136                     SELECT kohafield FROM marc_subfield_structure
2137                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2138                 |, undef, $marcfield, $marcsubfield);
2139
2140                 if ($kohafield) {
2141                     $column = $kohafield;
2142                 } else {
2143                     # MARC field is not linked to a DB field so we need to use
2144                     # ExtractValue on marcxml from biblio_metadata or
2145                     # items.more_subfields_xml, depending on the MARC field.
2146                     my $xpath;
2147                     my $sqlfield;
2148                     my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2149                     if ($marcfield eq $itemfield) {
2150                         $sqlfield = 'more_subfields_xml';
2151                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2152                     } else {
2153                         $sqlfield = 'metadata'; # From biblio_metadata
2154                         if ($marcfield < 10) {
2155                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2156                         } else {
2157                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2158                         }
2159                     }
2160                     $column = "ExtractValue($sqlfield, '$xpath')";
2161                 }
2162             } else {
2163                 $column = $field;
2164             }
2165
2166             if (ref $query eq 'ARRAY') {
2167                 if ($op eq '=') {
2168                     $op = 'IN';
2169                 } elsif ($op eq '!=') {
2170                     $op = 'NOT IN';
2171                 }
2172                 $where_fragment = {
2173                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
2174                     args => $query,
2175                 };
2176             } else {
2177                 $where_fragment = {
2178                     str => "$column $op ?",
2179                     args => [ $query ],
2180                 };
2181             }
2182         }
2183     }
2184
2185     return $where_fragment;
2186 }
2187
2188 =head2 SearchItems
2189
2190     my ($items, $total) = SearchItems($filter, $params);
2191
2192 Perform a search among items
2193
2194 $filter is a reference to a hash which can be a filter, or a combination of filters.
2195
2196 A filter has the following keys:
2197
2198 =over 2
2199
2200 =item * field: the name of a SQL column in table items
2201
2202 =item * query: the value to search in this column
2203
2204 =item * operator: comparison operator. Can be one of = != > < >= <= like
2205
2206 =back
2207
2208 A combination of filters hash the following keys:
2209
2210 =over 2
2211
2212 =item * conjunction: 'AND' or 'OR'
2213
2214 =item * filters: array ref of filters
2215
2216 =back
2217
2218 $params is a reference to a hash that can contain the following parameters:
2219
2220 =over 2
2221
2222 =item * rows: Number of items to return. 0 returns everything (default: 0)
2223
2224 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2225                (default: 1)
2226
2227 =item * sortby: A SQL column name in items table to sort on
2228
2229 =item * sortorder: 'ASC' or 'DESC'
2230
2231 =back
2232
2233 =cut
2234
2235 sub SearchItems {
2236     my ($filter, $params) = @_;
2237
2238     $filter //= {};
2239     $params //= {};
2240     return unless ref $filter eq 'HASH';
2241     return unless ref $params eq 'HASH';
2242
2243     # Default parameters
2244     $params->{rows} ||= 0;
2245     $params->{page} ||= 1;
2246     $params->{sortby} ||= 'itemnumber';
2247     $params->{sortorder} ||= 'ASC';
2248
2249     my ($where_str, @where_args);
2250     my $where_fragment = _SearchItems_build_where_fragment($filter);
2251     if ($where_fragment) {
2252         $where_str = $where_fragment->{str};
2253         @where_args = @{ $where_fragment->{args} };
2254     }
2255
2256     my $dbh = C4::Context->dbh;
2257     my $query = q{
2258         SELECT SQL_CALC_FOUND_ROWS items.*
2259         FROM items
2260           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2261           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2262           LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2263           WHERE 1
2264     };
2265     if (defined $where_str and $where_str ne '') {
2266         $query .= qq{ AND $where_str };
2267     }
2268
2269     $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
2270     push @where_args, C4::Context->preference('marcflavour');
2271
2272     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2273     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2274     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2275     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2276         ? $params->{sortby} : 'itemnumber';
2277     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2278     $query .= qq{ ORDER BY $sortby $sortorder };
2279
2280     my $rows = $params->{rows};
2281     my @limit_args;
2282     if ($rows > 0) {
2283         my $offset = $rows * ($params->{page}-1);
2284         $query .= qq { LIMIT ?, ? };
2285         push @limit_args, $offset, $rows;
2286     }
2287
2288     my $sth = $dbh->prepare($query);
2289     my $rv = $sth->execute(@where_args, @limit_args);
2290
2291     return unless ($rv);
2292     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2293
2294     return ($sth->fetchall_arrayref({}), $total_rows);
2295 }
2296
2297
2298 =head1  OTHER FUNCTIONS
2299
2300 =head2 _find_value
2301
2302   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2303
2304 Find the given $subfield in the given $tag in the given
2305 MARC::Record $record.  If the subfield is found, returns
2306 the (indicators, value) pair; otherwise, (undef, undef) is
2307 returned.
2308
2309 PROPOSITION :
2310 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2311 I suggest we export it from this module.
2312
2313 =cut
2314
2315 sub _find_value {
2316     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2317     my @result;
2318     my $indicator;
2319     if ( $tagfield < 10 ) {
2320         if ( $record->field($tagfield) ) {
2321             push @result, $record->field($tagfield)->data();
2322         } else {
2323             push @result, "";
2324         }
2325     } else {
2326         foreach my $field ( $record->field($tagfield) ) {
2327             my @subfields = $field->subfields();
2328             foreach my $subfield (@subfields) {
2329                 if ( @$subfield[0] eq $insubfield ) {
2330                     push @result, @$subfield[1];
2331                     $indicator = $field->indicator(1) . $field->indicator(2);
2332                 }
2333             }
2334         }
2335     }
2336     return ( $indicator, @result );
2337 }
2338
2339
2340 =head2 PrepareItemrecordDisplay
2341
2342   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2343
2344 Returns a hash with all the fields for Display a given item data in a template
2345
2346 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2347
2348 =cut
2349
2350 sub PrepareItemrecordDisplay {
2351
2352     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2353
2354     my $dbh = C4::Context->dbh;
2355     $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2356     my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2357
2358     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2359     # a shared data structure. No plugin (including custom ones) should change
2360     # its contents. See also GetMarcStructure.
2361     my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2362
2363     # return nothing if we don't have found an existing framework.
2364     return q{} unless $tagslib;
2365     my $itemrecord;
2366     if ($itemnum) {
2367         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2368     }
2369     my @loop_data;
2370
2371     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2372     my $query = qq{
2373         SELECT authorised_value,lib FROM authorised_values
2374     };
2375     $query .= qq{
2376         LEFT JOIN authorised_values_branches ON ( id = av_id )
2377     } if $branch_limit;
2378     $query .= qq{
2379         WHERE category = ?
2380     };
2381     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2382     $query .= qq{ ORDER BY lib};
2383     my $authorised_values_sth = $dbh->prepare( $query );
2384     foreach my $tag ( sort keys %{$tagslib} ) {
2385         if ( $tag ne '' ) {
2386
2387             # loop through each subfield
2388             my $cntsubf;
2389             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2390                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2391                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2392                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2393                 my %subfield_data;
2394                 $subfield_data{tag}           = $tag;
2395                 $subfield_data{subfield}      = $subfield;
2396                 $subfield_data{countsubfield} = $cntsubf++;
2397                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2398                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2399
2400                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2401                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2402                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2403                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2404                 $subfield_data{hidden}     = "display:none"
2405                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2406                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2407                 my ( $x, $defaultvalue );
2408                 if ($itemrecord) {
2409                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2410                 }
2411                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2412                 if ( !defined $defaultvalue ) {
2413                     $defaultvalue = q||;
2414                 } else {
2415                     $defaultvalue =~ s/"/&quot;/g;
2416                 }
2417
2418                 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
2419
2420                 # search for itemcallnumber if applicable
2421                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2422                     && C4::Context->preference('itemcallnumber') ) {
2423                     my $CNtag      = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2424                     my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2425                     if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2426                         $defaultvalue = $field->subfield($CNsubfield);
2427                     }
2428                 }
2429                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2430                     && $defaultvalues
2431                     && $defaultvalues->{'callnumber'} ) {
2432                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2433                         # if the item record exists, only use default value if the item has no callnumber
2434                         $defaultvalue = $defaultvalues->{callnumber};
2435                     } elsif ( !$itemrecord and $defaultvalues ) {
2436                         # if the item record *doesn't* exists, always use the default value
2437                         $defaultvalue = $defaultvalues->{callnumber};
2438                     }
2439                 }
2440                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2441                     && $defaultvalues
2442                     && $defaultvalues->{'branchcode'} ) {
2443                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2444                         $defaultvalue = $defaultvalues->{branchcode};
2445                     }
2446                 }
2447                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2448                     && $defaultvalues
2449                     && $defaultvalues->{'location'} ) {
2450
2451                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2452                         # if the item record exists, only use default value if the item has no locationr
2453                         $defaultvalue = $defaultvalues->{location};
2454                     } elsif ( !$itemrecord and $defaultvalues ) {
2455                         # if the item record *doesn't* exists, always use the default value
2456                         $defaultvalue = $defaultvalues->{location};
2457                     }
2458                 }
2459                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2460                     my @authorised_values;
2461                     my %authorised_lib;
2462
2463                     # builds list, depending on authorised value...
2464                     #---- branch
2465                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2466                         if (   ( C4::Context->preference("IndependentBranches") )
2467                             && !C4::Context->IsSuperLibrarian() ) {
2468                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2469                             $sth->execute( C4::Context->userenv->{branch} );
2470                             push @authorised_values, ""
2471                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2472                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2473                                 push @authorised_values, $branchcode;
2474                                 $authorised_lib{$branchcode} = $branchname;
2475                             }
2476                         } else {
2477                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2478                             $sth->execute;
2479                             push @authorised_values, ""
2480                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2481                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2482                                 push @authorised_values, $branchcode;
2483                                 $authorised_lib{$branchcode} = $branchname;
2484                             }
2485                         }
2486
2487                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2488                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2489                             $defaultvalue = $defaultvalues->{branchcode};
2490                         }
2491
2492                         #----- itemtypes
2493                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2494                         my $itemtypes = Koha::ItemTypes->search_with_localization;
2495                         push @authorised_values, ""
2496                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2497                         while ( my $itemtype = $itemtypes->next ) {
2498                             push @authorised_values, $itemtype->itemtype;
2499                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2500                         }
2501                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2502                             $defaultvalue = $defaultvalues->{'itemtype'};
2503                         }
2504
2505                         #---- class_sources
2506                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2507                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2508
2509                         my $class_sources = GetClassSources();
2510                         my $default_source = C4::Context->preference("DefaultClassificationSource");
2511
2512                         foreach my $class_source (sort keys %$class_sources) {
2513                             next unless $class_sources->{$class_source}->{'used'} or
2514                                         ($class_source eq $default_source);
2515                             push @authorised_values, $class_source;
2516                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2517                         }
2518
2519                         $defaultvalue = $default_source;
2520
2521                         #---- "true" authorised value
2522                     } else {
2523                         $authorised_values_sth->execute(
2524                             $tagslib->{$tag}->{$subfield}->{authorised_value},
2525                             $branch_limit ? $branch_limit : ()
2526                         );
2527                         push @authorised_values, ""
2528                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2529                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2530                             push @authorised_values, $value;
2531                             $authorised_lib{$value} = $lib;
2532                         }
2533                     }
2534                     $subfield_data{marc_value} = {
2535                         type    => 'select',
2536                         values  => \@authorised_values,
2537                         default => "$defaultvalue",
2538                         labels  => \%authorised_lib,
2539                     };
2540                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2541                 # it is a plugin
2542                     require Koha::FrameworkPlugin;
2543                     my $plugin = Koha::FrameworkPlugin->new({
2544                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
2545                         item_style => 1,
2546                     });
2547                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2548                     $plugin->build( $pars );
2549                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2550                         $defaultvalue = $field->subfield($subfield);
2551                     }
2552                     if( !$plugin->errstr ) {
2553                         #TODO Move html to template; see report 12176/13397
2554                         my $tab= $plugin->noclick? '-1': '';
2555                         my $class= $plugin->noclick? ' disabled': '';
2556                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
2557                         $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;
2558                     } else {
2559                         warn $plugin->errstr;
2560                         $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
2561                     }
2562                 }
2563                 elsif ( $tag eq '' ) {       # it's an hidden field
2564                     $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" />);
2565                 }
2566                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
2567                     $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" />);
2568                 }
2569                 elsif ( length($defaultvalue) > 100
2570                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2571                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
2572                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
2573                                   500 <= $tag && $tag < 600                     )
2574                           ) {
2575                     # oversize field (textarea)
2576                     $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");
2577                 } else {
2578                     $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"$maxlength\" />";
2579                 }
2580                 push( @loop_data, \%subfield_data );
2581             }
2582         }
2583     }
2584     my $itemnumber;
2585     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2586         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2587     }
2588     return {
2589         'itemtagfield'    => $itemtagfield,
2590         'itemtagsubfield' => $itemtagsubfield,
2591         'itemnumber'      => $itemnumber,
2592         'iteminformation' => \@loop_data
2593     };
2594 }
2595
2596 sub ToggleNewStatus {
2597     my ( $params ) = @_;
2598     my @rules = @{ $params->{rules} };
2599     my $report_only = $params->{report_only};
2600
2601     my $dbh = C4::Context->dbh;
2602     my @errors;
2603     my @item_columns = map { "items.$_" } Koha::Items->columns;
2604     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2605     my $report;
2606     for my $rule ( @rules ) {
2607         my $age = $rule->{age};
2608         my $conditions = $rule->{conditions};
2609         my $substitutions = $rule->{substitutions};
2610         foreach ( @$substitutions ) {
2611             ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2612         }
2613         my @params;
2614
2615         my $query = q|
2616             SELECT items.*
2617             FROM items
2618             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2619             WHERE 1
2620         |;
2621         for my $condition ( @$conditions ) {
2622             if (
2623                  grep {/^$condition->{field}$/} @item_columns
2624               or grep {/^$condition->{field}$/} @biblioitem_columns
2625             ) {
2626                 if ( $condition->{value} =~ /\|/ ) {
2627                     my @values = split /\|/, $condition->{value};
2628                     $query .= qq| AND $condition->{field} IN (|
2629                         . join( ',', ('?') x scalar @values )
2630                         . q|)|;
2631                     push @params, @values;
2632                 } else {
2633                     $query .= qq| AND $condition->{field} = ?|;
2634                     push @params, $condition->{value};
2635                 }
2636             }
2637         }
2638         if ( defined $age ) {
2639             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2640             push @params, $age;
2641         }
2642         my $sth = $dbh->prepare($query);
2643         $sth->execute( @params );
2644         while ( my $values = $sth->fetchrow_hashref ) {
2645             my $biblionumber = $values->{biblionumber};
2646             my $itemnumber = $values->{itemnumber};
2647             for my $substitution ( @$substitutions ) {
2648                 next unless $substitution->{field};
2649                 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2650                 C4::Items::ModItem( { $substitution->{item_field} => $substitution->{value} }, $biblionumber, $itemnumber )
2651                     unless $report_only;
2652                 push @{ $report->{$itemnumber} }, $substitution;
2653             }
2654         }
2655     }
2656
2657     return $report;
2658 }
2659
2660
2661 1;