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