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