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