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