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