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