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