Bug 27068: Perltidy _checkHoldPolicy
[koha.git] / C4 / Items.pm
1 package C4::Items;
2
3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use Modern::Perl;
22
23 use vars qw(@ISA @EXPORT);
24 BEGIN {
25     require Exporter;
26     @ISA = qw(Exporter);
27
28     @EXPORT = qw(
29         AddItemFromMarc
30         AddItemBatchFromMarc
31         ModItemFromMarc
32         Item2Marc
33         ModDateLastSeen
34         ModItemTransfer
35         CheckItemPreSave
36         GetItemsForInventory
37         GetItemsInfo
38         GetItemsLocationInfo
39         GetHostItemsInfo
40         get_hostitemnumbers_of
41         GetHiddenItemnumbers
42         MoveItemFromBiblio
43         CartToShelf
44         GetAnalyticsCount
45         SearchItems
46         PrepareItemrecordDisplay
47     );
48 }
49
50 use Carp;
51 use Try::Tiny;
52 use C4::Context;
53 use C4::Koha;
54 use C4::Biblio;
55 use Koha::DateUtils;
56 use MARC::Record;
57 use C4::ClassSource;
58 use C4::Log;
59 use List::MoreUtils qw(any);
60 use YAML qw(Load);
61 use DateTime::Format::MySQL;
62 use Data::Dumper; # used as part of logging item record changes, not just for
63                   # debugging; so please don't remove this
64
65 use Koha::AuthorisedValues;
66 use Koha::DateUtils qw(dt_from_string);
67 use Koha::Database;
68
69 use Koha::Biblioitems;
70 use Koha::Items;
71 use Koha::ItemTypes;
72 use Koha::SearchEngine;
73 use Koha::SearchEngine::Indexer;
74 use Koha::SearchEngine::Search;
75 use Koha::Libraries;
76
77 =head1 NAME
78
79 C4::Items - item management functions
80
81 =head1 DESCRIPTION
82
83 This module contains an API for manipulating item 
84 records in Koha, and is used by cataloguing, circulation,
85 acquisitions, and serials management.
86
87 # FIXME This POD is not up-to-date
88 A Koha item record is stored in two places: the
89 items table and embedded in a MARC tag in the XML
90 version of the associated bib record in C<biblioitems.marcxml>.
91 This is done to allow the item information to be readily
92 indexed (e.g., by Zebra), but means that each item
93 modification transaction must keep the items table
94 and the MARC XML in sync at all times.
95
96 The items table will be considered authoritative.  In other
97 words, if there is ever a discrepancy between the items
98 table and the MARC XML, the items table should be considered
99 accurate.
100
101 =head1 HISTORICAL NOTE
102
103 Most of the functions in C<C4::Items> were originally in
104 the C<C4::Biblio> module.
105
106 =head1 CORE EXPORTED FUNCTIONS
107
108 The following functions are meant for use by users
109 of C<C4::Items>
110
111 =cut
112
113 =head2 CartToShelf
114
115   CartToShelf($itemnumber);
116
117 Set the current shelving location of the item record
118 to its stored permanent shelving location.  This is
119 primarily used to indicate when an item whose current
120 location is a special processing ('PROC') or shelving cart
121 ('CART') location is back in the stacks.
122
123 =cut
124
125 sub CartToShelf {
126     my ( $itemnumber ) = @_;
127
128     unless ( $itemnumber ) {
129         croak "FAILED CartToShelf() - no itemnumber supplied";
130     }
131
132     my $item = Koha::Items->find($itemnumber);
133     if ( $item->location eq 'CART' ) {
134         $item->location($item->permanent_location)->store;
135     }
136 }
137
138 =head2 AddItemFromMarc
139
140   my ($biblionumber, $biblioitemnumber, $itemnumber) 
141       = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
142
143 Given a MARC::Record object containing an embedded item
144 record and a biblionumber, create a new item record.
145
146 The final optional parameter, C<$params>, expected to contain
147 'skip_record_index' key, which relayed down to Koha::Item/store,
148 there it prevents calling of index_records,
149 which takes most of the time in batch adds/deletes: index_records
150 to be called later in C<additem.pl> after the whole loop.
151
152 $params:
153     skip_record_index => 1|0
154
155 =cut
156
157 sub AddItemFromMarc {
158     my $source_item_marc = shift;
159     my $biblionumber     = shift;
160     my $params           = @_ ? shift : {};
161
162     my $dbh = C4::Context->dbh;
163
164     # parse item hash from MARC
165     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
166     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
167
168     my $localitemmarc = MARC::Record->new;
169     $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
170
171     my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
172     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
173     $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
174     $item_values->{biblionumber} = $biblionumber;
175     $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
176     $item_values->{cn_sort}   = delete $item_values->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
177     my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
178     return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
179 }
180
181 =head2 AddItemBatchFromMarc
182
183   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
184              $biblionumber, $biblioitemnumber, $frameworkcode);
185
186 Efficiently create item records from a MARC biblio record with
187 embedded item fields.  This routine is suitable for batch jobs.
188
189 This API assumes that the bib record has already been
190 saved to the C<biblio> and C<biblioitems> tables.  It does
191 not expect that C<biblio_metadata.metadata> is populated, but it
192 will do so via a call to ModBibiloMarc.
193
194 The goal of this API is to have a similar effect to using AddBiblio
195 and AddItems in succession, but without inefficient repeated
196 parsing of the MARC XML bib record.
197
198 This function returns an arrayref of new itemsnumbers and an arrayref of item
199 errors encountered during the processing.  Each entry in the errors
200 list is a hashref containing the following keys:
201
202 =over
203
204 =item item_sequence
205
206 Sequence number of original item tag in the MARC record.
207
208 =item item_barcode
209
210 Item barcode, provide to assist in the construction of
211 useful error messages.
212
213 =item error_code
214
215 Code representing the error condition.  Can be 'duplicate_barcode',
216 'invalid_homebranch', or 'invalid_holdingbranch'.
217
218 =item error_information
219
220 Additional information appropriate to the error condition.
221
222 =back
223
224 =cut
225
226 sub AddItemBatchFromMarc {
227     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
228     my @itemnumbers = ();
229     my @errors = ();
230     my $dbh = C4::Context->dbh;
231
232     # We modify the record, so lets work on a clone so we don't change the
233     # original.
234     $record = $record->clone();
235     # loop through the item tags and start creating items
236     my @bad_item_fields = ();
237     my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
238     my $item_sequence_num = 0;
239     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
240         $item_sequence_num++;
241         # we take the item field and stick it into a new
242         # MARC record -- this is required so far because (FIXME)
243         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
244         # and there is no TransformMarcFieldToKoha
245         my $temp_item_marc = MARC::Record->new();
246         $temp_item_marc->append_fields($item_field);
247     
248         # add biblionumber and biblioitemnumber
249         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
250         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
251         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
252         $item->{'biblionumber'} = $biblionumber;
253         $item->{'biblioitemnumber'} = $biblioitemnumber;
254         $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
255         $item->{cn_sort}   = delete $item->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
256
257         # check for duplicate barcode
258         my %item_errors = CheckItemPreSave($item);
259         if (%item_errors) {
260             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
261             push @bad_item_fields, $item_field;
262             next ITEMFIELD;
263         }
264
265         my $item_object = Koha::Item->new($item)->store;
266         push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
267
268         logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
269
270         my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
271         $item_field->replace_with($new_item_marc->field($itemtag));
272     }
273
274     # remove any MARC item fields for rejected items
275     foreach my $item_field (@bad_item_fields) {
276         $record->delete_field($item_field);
277     }
278
279     # update the MARC biblio
280  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
281
282     return (\@itemnumbers, \@errors);
283 }
284
285 =head2 ModItemFromMarc
286
287 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
288
289 The final optional parameter, C<$params>, expected to contain
290 'skip_record_index' key, which relayed down to Koha::Item/store,
291 there it prevents calling of index_records,
292 which takes most of the time in batch adds/deletes: index_records better
293 to be called later in C<additem.pl> after the whole loop.
294
295 $params:
296     skip_record_index => 1|0
297
298 =cut
299
300 sub ModItemFromMarc {
301     my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
302
303     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
304     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
305
306     my $localitemmarc = MARC::Record->new;
307     $localitemmarc->append_fields( $item_marc->field($itemtag) );
308     my $item_object = Koha::Items->find($itemnumber);
309     my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
310
311     # Retrieving the values for the fields that are not linked
312     my @mapped_fields = Koha::MarcSubfieldStructures->search(
313         {
314             frameworkcode => $frameworkcode,
315             kohafield     => { -like => "items.%" }
316         }
317     )->get_column('kohafield');
318     for my $c ( $item_object->_result->result_source->columns ) {
319         next if grep { "items.$c" eq $_ } @mapped_fields;
320         $item->{$c} = $item_object->$c;
321     }
322
323     $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
324     delete $item->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
325     $item->{itemnumber} = $itemnumber;
326     $item->{biblionumber} = $biblionumber;
327
328     my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
329                                                   # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
330     $item_object = $item_object->set_or_blank($item);
331     $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
332
333     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
334     $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
335     $item_object->store({ skip_record_index => $params->{skip_record_index} });
336
337     return $item_object->unblessed;
338 }
339
340 =head2 ModItemTransfer
341
342   ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
343
344 Marks an item as being transferred from one branch to another and records the trigger.
345
346 The last optional parameter allows for passing skip_record_index through to the items store call.
347
348 =cut
349
350 sub ModItemTransfer {
351     my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
352
353     my $dbh = C4::Context->dbh;
354     my $item = Koha::Items->find( $itemnumber );
355
356     # Remove the 'shelving cart' location status if it is being used.
357     CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
358
359     $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
360
361     #new entry in branchtransfers....
362     my $sth = $dbh->prepare(
363         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
364         VALUES (?, ?, NOW(), ?, ?)");
365     $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
366
367     # FIXME we are fetching the item twice in the 2 next statements!
368     Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
369     ModDateLastSeen($itemnumber, undef, { skip_record_index => $params->{skip_record_index} });
370     return;
371 }
372
373 =head2 ModDateLastSeen
374
375 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
376
377 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
378 C<$itemnumber> is the item number
379 C<$leave_item_lost> determines if a lost item will be found or remain lost
380
381 The last optional parameter allows for passing skip_record_index through to the items store call.
382
383 =cut
384
385 sub ModDateLastSeen {
386     my ( $itemnumber, $leave_item_lost, $params ) = @_;
387
388     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
389
390     my $item = Koha::Items->find($itemnumber);
391     $item->datelastseen($today);
392     $item->itemlost(0) unless $leave_item_lost;
393     $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
394 }
395
396 =head2 CheckItemPreSave
397
398     my $item_ref = TransformMarcToKoha($marc, 'items');
399     # do stuff
400     my %errors = CheckItemPreSave($item_ref);
401     if (exists $errors{'duplicate_barcode'}) {
402         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
403     } elsif (exists $errors{'invalid_homebranch'}) {
404         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
405     } elsif (exists $errors{'invalid_holdingbranch'}) {
406         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
407     } else {
408         print "item is OK";
409     }
410
411 Given a hashref containing item fields, determine if it can be
412 inserted or updated in the database.  Specifically, checks for
413 database integrity issues, and returns a hash containing any
414 of the following keys, if applicable.
415
416 =over 2
417
418 =item duplicate_barcode
419
420 Barcode, if it duplicates one already found in the database.
421
422 =item invalid_homebranch
423
424 Home branch, if not defined in branches table.
425
426 =item invalid_holdingbranch
427
428 Holding branch, if not defined in branches table.
429
430 =back
431
432 This function does NOT implement any policy-related checks,
433 e.g., whether current operator is allowed to save an
434 item that has a given branch code.
435
436 =cut
437
438 sub CheckItemPreSave {
439     my $item_ref = shift;
440
441     my %errors = ();
442
443     # check for duplicate barcode
444     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
445         my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
446         if ($existing_item) {
447             if (!exists $item_ref->{'itemnumber'}                       # new item
448                 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
449                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
450             }
451         }
452     }
453
454     # check for valid home branch
455     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
456         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
457         unless (defined $home_library) {
458             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
459         }
460     }
461
462     # check for valid holding branch
463     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
464         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
465         unless (defined $holding_library) {
466             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
467         }
468     }
469
470     return %errors;
471
472 }
473
474 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
475
476 The following functions provide various ways of 
477 getting an item record, a set of item records, or
478 lists of authorized values for certain item fields.
479
480 =cut
481
482 =head2 GetItemsForInventory
483
484 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
485   minlocation  => $minlocation,
486   maxlocation  => $maxlocation,
487   location     => $location,
488   itemtype     => $itemtype,
489   ignoreissued => $ignoreissued,
490   datelastseen => $datelastseen,
491   branchcode   => $branchcode,
492   branch       => $branch,
493   offset       => $offset,
494   size         => $size,
495   statushash   => $statushash,
496 } );
497
498 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
499
500 The sub returns a reference to a list of hashes, each containing
501 itemnumber, author, title, barcode, item callnumber, and date last
502 seen. It is ordered by callnumber then title.
503
504 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
505 the datelastseen can be used to specify that you want to see items not seen since a past date only.
506 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
507 $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.
508
509 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
510
511 =cut
512
513 sub GetItemsForInventory {
514     my ( $parameters ) = @_;
515     my $minlocation  = $parameters->{'minlocation'}  // '';
516     my $maxlocation  = $parameters->{'maxlocation'}  // '';
517     my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
518     my $location     = $parameters->{'location'}     // '';
519     my $itemtype     = $parameters->{'itemtype'}     // '';
520     my $ignoreissued = $parameters->{'ignoreissued'} // '';
521     my $datelastseen = $parameters->{'datelastseen'} // '';
522     my $branchcode   = $parameters->{'branchcode'}   // '';
523     my $branch       = $parameters->{'branch'}       // '';
524     my $offset       = $parameters->{'offset'}       // '';
525     my $size         = $parameters->{'size'}         // '';
526     my $statushash   = $parameters->{'statushash'}   // '';
527     my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
528
529     my $dbh = C4::Context->dbh;
530     my ( @bind_params, @where_strings );
531
532     my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
533     my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
534
535     my $select_columns = q{
536         SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
537     };
538     my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
539     my $query = q{
540         FROM items
541         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
542         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
543     };
544     if ($statushash){
545         for my $authvfield (keys %$statushash){
546             if ( scalar @{$statushash->{$authvfield}} > 0 ){
547                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
548                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
549             }
550         }
551     }
552
553     if ($minlocation) {
554         push @where_strings, 'items.cn_sort >= ?';
555         push @bind_params, $min_cnsort;
556     }
557
558     if ($maxlocation) {
559         push @where_strings, 'items.cn_sort <= ?';
560         push @bind_params, $max_cnsort;
561     }
562
563     if ($datelastseen) {
564         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
565         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
566         push @bind_params, $datelastseen;
567     }
568
569     if ( $location ) {
570         push @where_strings, 'items.location = ?';
571         push @bind_params, $location;
572     }
573
574     if ( $branchcode ) {
575         if($branch eq "homebranch"){
576         push @where_strings, 'items.homebranch = ?';
577         }else{
578             push @where_strings, 'items.holdingbranch = ?';
579         }
580         push @bind_params, $branchcode;
581     }
582
583     if ( $itemtype ) {
584         push @where_strings, 'biblioitems.itemtype = ?';
585         push @bind_params, $itemtype;
586     }
587
588     if ( $ignoreissued) {
589         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
590         push @where_strings, 'issues.date_due IS NULL';
591     }
592
593     if ( $ignore_waiting_holds ) {
594         $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
595         push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
596     }
597
598     if ( @where_strings ) {
599         $query .= 'WHERE ';
600         $query .= join ' AND ', @where_strings;
601     }
602     my $count_query = $select_count . $query;
603     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
604     $query .= " LIMIT $offset, $size" if ($offset and $size);
605     $query = $select_columns . $query;
606     my $sth = $dbh->prepare($query);
607     $sth->execute( @bind_params );
608
609     my @results = ();
610     my $tmpresults = $sth->fetchall_arrayref({});
611     $sth = $dbh->prepare( $count_query );
612     $sth->execute( @bind_params );
613     my ($iTotalRecords) = $sth->fetchrow_array();
614
615     my @avs = Koha::AuthorisedValues->search(
616         {   'marc_subfield_structures.kohafield' => { '>' => '' },
617             'me.authorised_value'                => { '>' => '' },
618         },
619         {   join     => { category => 'marc_subfield_structures' },
620             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
621             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
622             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
623         }
624     );
625
626     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
627
628     foreach my $row (@$tmpresults) {
629
630         # Auth values
631         foreach (keys %$row) {
632             if (
633                 defined(
634                     $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
635                 )
636             ) {
637                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
638             }
639         }
640         push @results, $row;
641     }
642
643     return (\@results, $iTotalRecords);
644 }
645
646 =head2 GetItemsInfo
647
648   @results = GetItemsInfo($biblionumber);
649
650 Returns information about items with the given biblionumber.
651
652 C<GetItemsInfo> returns a list of references-to-hash. Each element
653 contains a number of keys. Most of them are attributes from the
654 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
655 Koha database. Other keys include:
656
657 =over 2
658
659 =item C<$data-E<gt>{branchname}>
660
661 The name (not the code) of the branch to which the book belongs.
662
663 =item C<$data-E<gt>{datelastseen}>
664
665 This is simply C<items.datelastseen>, except that while the date is
666 stored in YYYY-MM-DD format in the database, here it is converted to
667 DD/MM/YYYY format. A NULL date is returned as C<//>.
668
669 =item C<$data-E<gt>{datedue}>
670
671 =item C<$data-E<gt>{class}>
672
673 This is the concatenation of C<biblioitems.classification>, the book's
674 Dewey code, and C<biblioitems.subclass>.
675
676 =item C<$data-E<gt>{ocount}>
677
678 I think this is the number of copies of the book available.
679
680 =item C<$data-E<gt>{order}>
681
682 If this is set, it is set to C<One Order>.
683
684 =back
685
686 =cut
687
688 sub GetItemsInfo {
689     my ( $biblionumber ) = @_;
690     my $dbh   = C4::Context->dbh;
691     require C4::Languages;
692     my $language = C4::Languages::getlanguage();
693     my $query = "
694     SELECT items.*,
695            biblio.*,
696            biblioitems.volume,
697            biblioitems.number,
698            biblioitems.itemtype,
699            biblioitems.isbn,
700            biblioitems.issn,
701            biblioitems.publicationyear,
702            biblioitems.publishercode,
703            biblioitems.volumedate,
704            biblioitems.volumedesc,
705            biblioitems.lccn,
706            biblioitems.url,
707            items.notforloan as itemnotforloan,
708            issues.borrowernumber,
709            issues.date_due as datedue,
710            issues.onsite_checkout,
711            borrowers.cardnumber,
712            borrowers.surname,
713            borrowers.firstname,
714            borrowers.branchcode as bcode,
715            serial.serialseq,
716            serial.publisheddate,
717            itemtypes.description,
718            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
719            itemtypes.notforloan as notforloan_per_itemtype,
720            holding.branchurl,
721            holding.branchcode,
722            holding.branchname,
723            holding.opac_info as holding_branch_opac_info,
724            home.opac_info as home_branch_opac_info,
725            IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
726      FROM items
727      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
728      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
729      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
730      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
731      LEFT JOIN issues USING (itemnumber)
732      LEFT JOIN borrowers USING (borrowernumber)
733      LEFT JOIN serialitems USING (itemnumber)
734      LEFT JOIN serial USING (serialid)
735      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
736      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
737     $query .= q|
738     LEFT JOIN tmp_holdsqueue USING (itemnumber)
739     LEFT JOIN localization ON itemtypes.itemtype = localization.code
740         AND localization.entity = 'itemtypes'
741         AND localization.lang = ?
742     |;
743
744     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
745     my $sth = $dbh->prepare($query);
746     $sth->execute($language, $biblionumber);
747     my $i = 0;
748     my @results;
749     my $serial;
750
751     my $userenv = C4::Context->userenv;
752     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
753     while ( my $data = $sth->fetchrow_hashref ) {
754         if ( $data->{borrowernumber} && $want_not_same_branch) {
755             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
756         }
757
758         $serial ||= $data->{'serial'};
759
760         my $descriptions;
761         # get notforloan complete status if applicable
762         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
763         $data->{notforloanvalue}     = $descriptions->{lib} // '';
764         $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
765
766         # get restricted status and description if applicable
767         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
768         $data->{restrictedvalue}     = $descriptions->{lib} // '';
769         $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
770
771         # my stack procedures
772         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
773         $data->{stack}          = $descriptions->{lib} // '';
774
775         # Find the last 3 people who borrowed this item.
776         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
777                                     WHERE itemnumber = ?
778                                     AND old_issues.borrowernumber = borrowers.borrowernumber
779                                     ORDER BY returndate DESC
780                                     LIMIT 3");
781         $sth2->execute($data->{'itemnumber'});
782         my $ii = 0;
783         while (my $data2 = $sth2->fetchrow_hashref()) {
784             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
785             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
786             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
787             $ii++;
788         }
789
790         $results[$i] = $data;
791         $i++;
792     }
793
794     return $serial
795         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
796         : @results;
797 }
798
799 =head2 GetItemsLocationInfo
800
801   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
802
803 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
804
805 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
806
807 =over 2
808
809 =item C<$data-E<gt>{homebranch}>
810
811 Branch Name of the item's homebranch
812
813 =item C<$data-E<gt>{holdingbranch}>
814
815 Branch Name of the item's holdingbranch
816
817 =item C<$data-E<gt>{location}>
818
819 Item's shelving location code
820
821 =item C<$data-E<gt>{location_intranet}>
822
823 The intranet description for the Shelving Location as set in authorised_values 'LOC'
824
825 =item C<$data-E<gt>{location_opac}>
826
827 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
828 description is set.
829
830 =item C<$data-E<gt>{itemcallnumber}>
831
832 Item's itemcallnumber
833
834 =item C<$data-E<gt>{cn_sort}>
835
836 Item's call number normalized for sorting
837
838 =back
839   
840 =cut
841
842 sub GetItemsLocationInfo {
843         my $biblionumber = shift;
844         my @results;
845
846         my $dbh = C4::Context->dbh;
847         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
848                             location, itemcallnumber, cn_sort
849                      FROM items, branches as a, branches as b
850                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
851                      AND biblionumber = ?
852                      ORDER BY cn_sort ASC";
853         my $sth = $dbh->prepare($query);
854         $sth->execute($biblionumber);
855
856         while ( my $data = $sth->fetchrow_hashref ) {
857              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
858              $av = $av->count ? $av->next : undef;
859              $data->{location_intranet} = $av ? $av->lib : '';
860              $data->{location_opac}     = $av ? $av->opac_description : '';
861              push @results, $data;
862         }
863         return @results;
864 }
865
866 =head2 GetHostItemsInfo
867
868     $hostiteminfo = GetHostItemsInfo($hostfield);
869     Returns the iteminfo for items linked to records via a host field
870
871 =cut
872
873 sub GetHostItemsInfo {
874     my ($record) = @_;
875     my @returnitemsInfo;
876
877     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
878         return @returnitemsInfo;
879     }
880
881     my @fields;
882     if( C4::Context->preference('marcflavour') eq 'MARC21' ||
883       C4::Context->preference('marcflavour') eq 'NORMARC') {
884         @fields = $record->field('773');
885     } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
886         @fields = $record->field('461');
887     }
888
889     foreach my $hostfield ( @fields ) {
890         my $hostbiblionumber = $hostfield->subfield("0");
891         my $linkeditemnumber = $hostfield->subfield("9");
892         my @hostitemInfos = GetItemsInfo($hostbiblionumber);
893         foreach my $hostitemInfo (@hostitemInfos) {
894             if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
895                 push @returnitemsInfo, $hostitemInfo;
896                 last;
897             }
898         }
899     }
900     return @returnitemsInfo;
901 }
902
903 =head2 get_hostitemnumbers_of
904
905   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
906
907 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
908
909 Return a reference on a hash where key is a biblionumber and values are
910 references on array of itemnumbers.
911
912 =cut
913
914
915 sub get_hostitemnumbers_of {
916     my ($biblionumber) = @_;
917
918     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
919         return ();
920     }
921
922     my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
923     return unless $marcrecord;
924
925     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
926
927     my $marcflavor = C4::Context->preference('marcflavour');
928     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
929         $tag      = '773';
930         $biblio_s = '0';
931         $item_s   = '9';
932     }
933     elsif ( $marcflavor eq 'UNIMARC' ) {
934         $tag      = '461';
935         $biblio_s = '0';
936         $item_s   = '9';
937     }
938
939     foreach my $hostfield ( $marcrecord->field($tag) ) {
940         my $hostbiblionumber = $hostfield->subfield($biblio_s);
941         next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
942         my $linkeditemnumber = $hostfield->subfield($item_s);
943         if ( ! $linkeditemnumber ) {
944             warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
945             next;
946         }
947         my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
948         push @returnhostitemnumbers, $linkeditemnumber
949           if $is_from_biblio;
950     }
951
952     return @returnhostitemnumbers;
953 }
954
955 =head2 GetHiddenItemnumbers
956
957     my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
958
959 Given a list of items it checks which should be hidden from the OPAC given
960 the current configuration. Returns a list of itemnumbers corresponding to
961 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
962 to be excluded
963
964 =cut
965
966 sub GetHiddenItemnumbers {
967     my $params = shift;
968     my $items = $params->{items};
969     if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
970         foreach my $except (split(/\|/, $exceptions)){
971             if ($params->{'borcat'} eq $except){
972                 return; # we don't hide anything for this borrower category
973             }
974         }
975     }
976     my @resultitems;
977
978     my $yaml = C4::Context->preference('OpacHiddenItems');
979     return () if (! $yaml =~ /\S/ );
980     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
981     my $hidingrules;
982     eval {
983         $hidingrules = YAML::Load($yaml);
984     };
985     if ($@) {
986         warn "Unable to parse OpacHiddenItems syspref : $@";
987         return ();
988     }
989     my $dbh = C4::Context->dbh;
990
991     # For each item
992     foreach my $item (@$items) {
993
994         # We check each rule
995         foreach my $field (keys %$hidingrules) {
996             my $val;
997             if (exists $item->{$field}) {
998                 $val = $item->{$field};
999             }
1000             else {
1001                 my $query = "SELECT $field from items where itemnumber = ?";
1002                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1003             }
1004             $val = '' unless defined $val;
1005
1006             # If the results matches the values in the yaml file
1007             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1008
1009                 # We add the itemnumber to the list
1010                 push @resultitems, $item->{'itemnumber'};
1011
1012                 # If at least one rule matched for an item, no need to test the others
1013                 last;
1014             }
1015         }
1016     }
1017     return @resultitems;
1018 }
1019
1020 =head1 LIMITED USE FUNCTIONS
1021
1022 The following functions, while part of the public API,
1023 are not exported.  This is generally because they are
1024 meant to be used by only one script for a specific
1025 purpose, and should not be used in any other context
1026 without careful thought.
1027
1028 =cut
1029
1030 =head2 GetMarcItem
1031
1032   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1033
1034 Returns MARC::Record of the item passed in parameter.
1035 This function is meant for use only in C<cataloguing/additem.pl>,
1036 where it is needed to support that script's MARC-like
1037 editor.
1038
1039 =cut
1040
1041 sub GetMarcItem {
1042     my ( $biblionumber, $itemnumber ) = @_;
1043
1044     # GetMarcItem has been revised so that it does the following:
1045     #  1. Gets the item information from the items table.
1046     #  2. Converts it to a MARC field for storage in the bib record.
1047     #
1048     # The previous behavior was:
1049     #  1. Get the bib record.
1050     #  2. Return the MARC tag corresponding to the item record.
1051     #
1052     # The difference is that one treats the items row as authoritative,
1053     # while the other treats the MARC representation as authoritative
1054     # under certain circumstances.
1055
1056     my $item = Koha::Items->find($itemnumber) or return;
1057
1058     # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1059     # Also, don't emit a subfield if the underlying field is blank.
1060
1061     return Item2Marc($item->unblessed, $biblionumber);
1062
1063 }
1064 sub Item2Marc {
1065         my ($itemrecord,$biblionumber)=@_;
1066     my $mungeditem = { 
1067         map {  
1068             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1069         } keys %{ $itemrecord } 
1070     };
1071     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1072     my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1073     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1074         "items.itemnumber", $framework,
1075     );
1076
1077     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1078     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1079                 foreach my $field ($itemmarc->field($itemtag)){
1080             $field->add_subfields(@$unlinked_item_subfields);
1081         }
1082     }
1083         return $itemmarc;
1084 }
1085
1086 =head1 PRIVATE FUNCTIONS AND VARIABLES
1087
1088 The following functions are not meant to be called
1089 directly, but are documented in order to explain
1090 the inner workings of C<C4::Items>.
1091
1092 =cut
1093
1094 =head2 MoveItemFromBiblio
1095
1096   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1097
1098 Moves an item from a biblio to another
1099
1100 Returns undef if the move failed or the biblionumber of the destination record otherwise
1101
1102 =cut
1103
1104 sub MoveItemFromBiblio {
1105     my ($itemnumber, $frombiblio, $tobiblio) = @_;
1106     my $dbh = C4::Context->dbh;
1107     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1108         SELECT biblioitemnumber
1109         FROM biblioitems
1110         WHERE biblionumber = ?
1111     |, undef, $tobiblio );
1112     my $return = $dbh->do(q|
1113         UPDATE items
1114         SET biblioitemnumber = ?,
1115             biblionumber = ?
1116         WHERE itemnumber = ?
1117             AND biblionumber = ?
1118     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1119     if ($return == 1) {
1120         my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1121         $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
1122         $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
1123             # Checking if the item we want to move is in an order 
1124         require C4::Acquisition;
1125         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1126             if ($order) {
1127                     # Replacing the biblionumber within the order if necessary
1128                     $order->{'biblionumber'} = $tobiblio;
1129                 C4::Acquisition::ModOrder($order);
1130             }
1131
1132         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1133         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1134             $dbh->do( qq|
1135                 UPDATE $table_name
1136                 SET biblionumber = ?
1137                 WHERE itemnumber = ?
1138             |, undef, $tobiblio, $itemnumber );
1139         }
1140         return $tobiblio;
1141         }
1142     return;
1143 }
1144
1145 =head2 _marc_from_item_hash
1146
1147   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1148
1149 Given an item hash representing a complete item record,
1150 create a C<MARC::Record> object containing an embedded
1151 tag representing that item.
1152
1153 The third, optional parameter C<$unlinked_item_subfields> is
1154 an arrayref of subfields (not mapped to C<items> fields per the
1155 framework) to be added to the MARC representation
1156 of the item.
1157
1158 =cut
1159
1160 sub _marc_from_item_hash {
1161     my $item = shift;
1162     my $frameworkcode = shift;
1163     my $unlinked_item_subfields;
1164     if (@_) {
1165         $unlinked_item_subfields = shift;
1166     }
1167    
1168     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1169     # Also, don't emit a subfield if the underlying field is blank.
1170     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1171                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1172                                 : ()  } keys %{ $item } }; 
1173
1174     my $item_marc = MARC::Record->new();
1175     foreach my $item_field ( keys %{$mungeditem} ) {
1176         my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1177         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
1178         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1179         foreach my $value (@values){
1180             if ( my $field = $item_marc->field($tag) ) {
1181                     $field->add_subfields( $subfield => $value );
1182             } else {
1183                 my $add_subfields = [];
1184                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1185                     $add_subfields = $unlinked_item_subfields;
1186             }
1187             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1188             }
1189         }
1190     }
1191
1192     return $item_marc;
1193 }
1194
1195 =head2 _repack_item_errors
1196
1197 Add an error message hash generated by C<CheckItemPreSave>
1198 to a list of errors.
1199
1200 =cut
1201
1202 sub _repack_item_errors {
1203     my $item_sequence_num = shift;
1204     my $item_ref = shift;
1205     my $error_ref = shift;
1206
1207     my @repacked_errors = ();
1208
1209     foreach my $error_code (sort keys %{ $error_ref }) {
1210         my $repacked_error = {};
1211         $repacked_error->{'item_sequence'} = $item_sequence_num;
1212         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1213         $repacked_error->{'error_code'} = $error_code;
1214         $repacked_error->{'error_information'} = $error_ref->{$error_code};
1215         push @repacked_errors, $repacked_error;
1216     } 
1217
1218     return @repacked_errors;
1219 }
1220
1221 =head2 _get_unlinked_item_subfields
1222
1223   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1224
1225 =cut
1226
1227 sub _get_unlinked_item_subfields {
1228     my $original_item_marc = shift;
1229     my $frameworkcode = shift;
1230
1231     my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1232
1233     # assume that this record has only one field, and that that
1234     # field contains only the item information
1235     my $subfields = [];
1236     my @fields = $original_item_marc->fields();
1237     if ($#fields > -1) {
1238         my $field = $fields[0];
1239             my $tag = $field->tag();
1240         foreach my $subfield ($field->subfields()) {
1241             if (defined $subfield->[1] and
1242                 $subfield->[1] ne '' and
1243                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1244                 push @$subfields, $subfield->[0] => $subfield->[1];
1245             }
1246         }
1247     }
1248     return $subfields;
1249 }
1250
1251 =head2 _get_unlinked_subfields_xml
1252
1253   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1254
1255 =cut
1256
1257 sub _get_unlinked_subfields_xml {
1258     my $unlinked_item_subfields = shift;
1259
1260     my $xml;
1261     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1262         my $marc = MARC::Record->new();
1263         # use of tag 999 is arbitrary, and doesn't need to match the item tag
1264         # used in the framework
1265         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1266         $marc->encoding("UTF-8");    
1267         $xml = $marc->as_xml("USMARC");
1268     }
1269
1270     return $xml;
1271 }
1272
1273 =head2 _parse_unlinked_item_subfields_from_xml
1274
1275   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1276
1277 =cut
1278
1279 sub  _parse_unlinked_item_subfields_from_xml {
1280     my $xml = shift;
1281     require C4::Charset;
1282     return unless defined $xml and $xml ne "";
1283     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1284     my $unlinked_subfields = [];
1285     my @fields = $marc->fields();
1286     if ($#fields > -1) {
1287         foreach my $subfield ($fields[0]->subfields()) {
1288             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1289         }
1290     }
1291     return $unlinked_subfields;
1292 }
1293
1294 =head2 GetAnalyticsCount
1295
1296   $count= &GetAnalyticsCount($itemnumber)
1297
1298 counts Usage of itemnumber in Analytical bibliorecords. 
1299
1300 =cut
1301
1302 sub GetAnalyticsCount {
1303     my ($itemnumber) = @_;
1304
1305     ### ZOOM search here
1306     my $query;
1307     $query= "hi=".$itemnumber;
1308     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1309     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1310     return ($result);
1311 }
1312
1313 sub _SearchItems_build_where_fragment {
1314     my ($filter) = @_;
1315
1316     my $dbh = C4::Context->dbh;
1317
1318     my $where_fragment;
1319     if (exists($filter->{conjunction})) {
1320         my (@where_strs, @where_args);
1321         foreach my $f (@{ $filter->{filters} }) {
1322             my $fragment = _SearchItems_build_where_fragment($f);
1323             if ($fragment) {
1324                 push @where_strs, $fragment->{str};
1325                 push @where_args, @{ $fragment->{args} };
1326             }
1327         }
1328         my $where_str = '';
1329         if (@where_strs) {
1330             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1331             $where_fragment = {
1332                 str => $where_str,
1333                 args => \@where_args,
1334             };
1335         }
1336     } else {
1337         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1338         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1339         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1340         my @operators = qw(= != > < >= <= like);
1341         push @operators, 'not like';
1342         my $field = $filter->{field} // q{};
1343         if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1344             my $op = $filter->{operator};
1345             my $query = $filter->{query};
1346             my $ifnull = $filter->{ifnull};
1347
1348             if (!$op or (0 == grep { $_ eq $op } @operators)) {
1349                 $op = '='; # default operator
1350             }
1351
1352             my $column;
1353             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1354                 my $marcfield = $1;
1355                 my $marcsubfield = $2;
1356                 my ($kohafield) = $dbh->selectrow_array(q|
1357                     SELECT kohafield FROM marc_subfield_structure
1358                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1359                 |, undef, $marcfield, $marcsubfield);
1360
1361                 if ($kohafield) {
1362                     $column = $kohafield;
1363                 } else {
1364                     # MARC field is not linked to a DB field so we need to use
1365                     # ExtractValue on marcxml from biblio_metadata or
1366                     # items.more_subfields_xml, depending on the MARC field.
1367                     my $xpath;
1368                     my $sqlfield;
1369                     my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1370                     if ($marcfield eq $itemfield) {
1371                         $sqlfield = 'more_subfields_xml';
1372                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1373                     } else {
1374                         $sqlfield = 'metadata'; # From biblio_metadata
1375                         if ($marcfield < 10) {
1376                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1377                         } else {
1378                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1379                         }
1380                     }
1381                     $column = "ExtractValue($sqlfield, '$xpath')";
1382                 }
1383             } else {
1384                 $column = $field;
1385             }
1386
1387             if ( defined $ifnull ) {
1388                 $column = "COALESCE($column, ?)";
1389             }
1390
1391             if (ref $query eq 'ARRAY') {
1392                 if ($op eq '=') {
1393                     $op = 'IN';
1394                 } elsif ($op eq '!=') {
1395                     $op = 'NOT IN';
1396                 }
1397                 $where_fragment = {
1398                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
1399                     args => $query,
1400                 };
1401             } else {
1402                 $where_fragment = {
1403                     str => "$column $op ?",
1404                     args => [ $query ],
1405                 };
1406             }
1407
1408             if ( defined $ifnull ) {
1409                 unshift @{ $where_fragment->{args} }, $ifnull;
1410             }
1411         }
1412     }
1413
1414     return $where_fragment;
1415 }
1416
1417 =head2 SearchItems
1418
1419     my ($items, $total) = SearchItems($filter, $params);
1420
1421 Perform a search among items
1422
1423 $filter is a reference to a hash which can be a filter, or a combination of filters.
1424
1425 A filter has the following keys:
1426
1427 =over 2
1428
1429 =item * field: the name of a SQL column in table items
1430
1431 =item * query: the value to search in this column
1432
1433 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1434
1435 =back
1436
1437 A combination of filters hash the following keys:
1438
1439 =over 2
1440
1441 =item * conjunction: 'AND' or 'OR'
1442
1443 =item * filters: array ref of filters
1444
1445 =back
1446
1447 $params is a reference to a hash that can contain the following parameters:
1448
1449 =over 2
1450
1451 =item * rows: Number of items to return. 0 returns everything (default: 0)
1452
1453 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1454                (default: 1)
1455
1456 =item * sortby: A SQL column name in items table to sort on
1457
1458 =item * sortorder: 'ASC' or 'DESC'
1459
1460 =back
1461
1462 =cut
1463
1464 sub SearchItems {
1465     my ($filter, $params) = @_;
1466
1467     $filter //= {};
1468     $params //= {};
1469     return unless ref $filter eq 'HASH';
1470     return unless ref $params eq 'HASH';
1471
1472     # Default parameters
1473     $params->{rows} ||= 0;
1474     $params->{page} ||= 1;
1475     $params->{sortby} ||= 'itemnumber';
1476     $params->{sortorder} ||= 'ASC';
1477
1478     my ($where_str, @where_args);
1479     my $where_fragment = _SearchItems_build_where_fragment($filter);
1480     if ($where_fragment) {
1481         $where_str = $where_fragment->{str};
1482         @where_args = @{ $where_fragment->{args} };
1483     }
1484
1485     my $dbh = C4::Context->dbh;
1486     my $query = q{
1487         SELECT SQL_CALC_FOUND_ROWS items.*
1488         FROM items
1489           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1490           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1491           LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1492           WHERE 1
1493     };
1494     if (defined $where_str and $where_str ne '') {
1495         $query .= qq{ AND $where_str };
1496     }
1497
1498     $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1499     push @where_args, C4::Context->preference('marcflavour');
1500
1501     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1502     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1503     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1504     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1505         ? $params->{sortby} : 'itemnumber';
1506     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1507     $query .= qq{ ORDER BY $sortby $sortorder };
1508
1509     my $rows = $params->{rows};
1510     my @limit_args;
1511     if ($rows > 0) {
1512         my $offset = $rows * ($params->{page}-1);
1513         $query .= qq { LIMIT ?, ? };
1514         push @limit_args, $offset, $rows;
1515     }
1516
1517     my $sth = $dbh->prepare($query);
1518     my $rv = $sth->execute(@where_args, @limit_args);
1519
1520     return unless ($rv);
1521     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1522
1523     return ($sth->fetchall_arrayref({}), $total_rows);
1524 }
1525
1526
1527 =head1  OTHER FUNCTIONS
1528
1529 =head2 _find_value
1530
1531   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1532
1533 Find the given $subfield in the given $tag in the given
1534 MARC::Record $record.  If the subfield is found, returns
1535 the (indicators, value) pair; otherwise, (undef, undef) is
1536 returned.
1537
1538 PROPOSITION :
1539 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1540 I suggest we export it from this module.
1541
1542 =cut
1543
1544 sub _find_value {
1545     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1546     my @result;
1547     my $indicator;
1548     if ( $tagfield < 10 ) {
1549         if ( $record->field($tagfield) ) {
1550             push @result, $record->field($tagfield)->data();
1551         } else {
1552             push @result, "";
1553         }
1554     } else {
1555         foreach my $field ( $record->field($tagfield) ) {
1556             my @subfields = $field->subfields();
1557             foreach my $subfield (@subfields) {
1558                 if ( @$subfield[0] eq $insubfield ) {
1559                     push @result, @$subfield[1];
1560                     $indicator = $field->indicator(1) . $field->indicator(2);
1561                 }
1562             }
1563         }
1564     }
1565     return ( $indicator, @result );
1566 }
1567
1568
1569 =head2 PrepareItemrecordDisplay
1570
1571   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1572
1573 Returns a hash with all the fields for Display a given item data in a template
1574
1575 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1576
1577 =cut
1578
1579 sub PrepareItemrecordDisplay {
1580
1581     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1582
1583     my $dbh = C4::Context->dbh;
1584     $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1585     my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1586
1587     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1588     # a shared data structure. No plugin (including custom ones) should change
1589     # its contents. See also GetMarcStructure.
1590     my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1591
1592     # return nothing if we don't have found an existing framework.
1593     return q{} unless $tagslib;
1594     my $itemrecord;
1595     if ($itemnum) {
1596         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1597     }
1598     my @loop_data;
1599
1600     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1601     my $query = qq{
1602         SELECT authorised_value,lib FROM authorised_values
1603     };
1604     $query .= qq{
1605         LEFT JOIN authorised_values_branches ON ( id = av_id )
1606     } if $branch_limit;
1607     $query .= qq{
1608         WHERE category = ?
1609     };
1610     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1611     $query .= qq{ ORDER BY lib};
1612     my $authorised_values_sth = $dbh->prepare( $query );
1613     foreach my $tag ( sort keys %{$tagslib} ) {
1614         if ( $tag ne '' ) {
1615
1616             # loop through each subfield
1617             my $cntsubf;
1618             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1619                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1620                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1621                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1622                 my %subfield_data;
1623                 $subfield_data{tag}           = $tag;
1624                 $subfield_data{subfield}      = $subfield;
1625                 $subfield_data{countsubfield} = $cntsubf++;
1626                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1627                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1628
1629                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1630                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
1631                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
1632                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1633                 $subfield_data{hidden}     = "display:none"
1634                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1635                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1636                 my ( $x, $defaultvalue );
1637                 if ($itemrecord) {
1638                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1639                 }
1640                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1641                 if ( !defined $defaultvalue ) {
1642                     $defaultvalue = q||;
1643                 } else {
1644                     $defaultvalue =~ s/"/&quot;/g;
1645                     # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1646                     my $today_dt = dt_from_string;
1647                     my $year     = $today_dt->strftime('%Y');
1648                     my $shortyear     = $today_dt->strftime('%y');
1649                     my $month    = $today_dt->strftime('%m');
1650                     my $day      = $today_dt->strftime('%d');
1651                     $defaultvalue =~ s/<<YYYY>>/$year/g;
1652                     $defaultvalue =~ s/<<YY>>/$shortyear/g;
1653                     $defaultvalue =~ s/<<MM>>/$month/g;
1654                     $defaultvalue =~ s/<<DD>>/$day/g;
1655
1656                     # And <<USER>> with surname (?)
1657                     my $username =
1658                       (   C4::Context->userenv
1659                         ? C4::Context->userenv->{'surname'}
1660                         : "superlibrarian" );
1661                     $defaultvalue =~ s/<<USER>>/$username/g;
1662                 }
1663
1664                 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1665
1666                 # search for itemcallnumber if applicable
1667                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1668                     && C4::Context->preference('itemcallnumber') && $itemrecord) {
1669                     foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1670                         my $CNtag      = substr( $itemcn_pref, 0, 3 );
1671                         next unless my $field = $itemrecord->field($CNtag);
1672                         my $CNsubfields = substr( $itemcn_pref, 3 );
1673                         $defaultvalue = $field->as_string( $CNsubfields, ' ');
1674                         last if $defaultvalue;
1675                     }
1676                 }
1677                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1678                     && $defaultvalues
1679                     && $defaultvalues->{'callnumber'} ) {
1680                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1681                         # if the item record exists, only use default value if the item has no callnumber
1682                         $defaultvalue = $defaultvalues->{callnumber};
1683                     } elsif ( !$itemrecord and $defaultvalues ) {
1684                         # if the item record *doesn't* exists, always use the default value
1685                         $defaultvalue = $defaultvalues->{callnumber};
1686                     }
1687                 }
1688                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
1689                     && $defaultvalues
1690                     && $defaultvalues->{'branchcode'} ) {
1691                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1692                         $defaultvalue = $defaultvalues->{branchcode};
1693                     }
1694                 }
1695                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
1696                     && $defaultvalues
1697                     && $defaultvalues->{'location'} ) {
1698
1699                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1700                         # if the item record exists, only use default value if the item has no locationr
1701                         $defaultvalue = $defaultvalues->{location};
1702                     } elsif ( !$itemrecord and $defaultvalues ) {
1703                         # if the item record *doesn't* exists, always use the default value
1704                         $defaultvalue = $defaultvalues->{location};
1705                     }
1706                 }
1707                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
1708                     my @authorised_values;
1709                     my %authorised_lib;
1710
1711                     # builds list, depending on authorised value...
1712                     #---- branch
1713                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1714                         if (   ( C4::Context->preference("IndependentBranches") )
1715                             && !C4::Context->IsSuperLibrarian() ) {
1716                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1717                             $sth->execute( C4::Context->userenv->{branch} );
1718                             push @authorised_values, ""
1719                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1720                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1721                                 push @authorised_values, $branchcode;
1722                                 $authorised_lib{$branchcode} = $branchname;
1723                             }
1724                         } else {
1725                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1726                             $sth->execute;
1727                             push @authorised_values, ""
1728                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1729                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1730                                 push @authorised_values, $branchcode;
1731                                 $authorised_lib{$branchcode} = $branchname;
1732                             }
1733                         }
1734
1735                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1736                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1737                             $defaultvalue = $defaultvalues->{branchcode};
1738                         }
1739
1740                         #----- itemtypes
1741                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
1742                         my $itemtypes = Koha::ItemTypes->search_with_localization;
1743                         push @authorised_values, "";
1744                         while ( my $itemtype = $itemtypes->next ) {
1745                             push @authorised_values, $itemtype->itemtype;
1746                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1747                         }
1748                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1749                             $defaultvalue = $defaultvalues->{'itemtype'};
1750                         }
1751
1752                         #---- class_sources
1753                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
1754                         push @authorised_values, "";
1755
1756                         my $class_sources = GetClassSources();
1757                         my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1758
1759                         foreach my $class_source (sort keys %$class_sources) {
1760                             next unless $class_sources->{$class_source}->{'used'} or
1761                                         ($class_source eq $default_source);
1762                             push @authorised_values, $class_source;
1763                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1764                         }
1765
1766                         $defaultvalue = $default_source;
1767
1768                         #---- "true" authorised value
1769                     } else {
1770                         $authorised_values_sth->execute(
1771                             $tagslib->{$tag}->{$subfield}->{authorised_value},
1772                             $branch_limit ? $branch_limit : ()
1773                         );
1774                         push @authorised_values, "";
1775                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1776                             push @authorised_values, $value;
1777                             $authorised_lib{$value} = $lib;
1778                         }
1779                     }
1780                     $subfield_data{marc_value} = {
1781                         type    => 'select',
1782                         values  => \@authorised_values,
1783                         default => $defaultvalue // q{},
1784                         labels  => \%authorised_lib,
1785                     };
1786                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
1787                 # it is a plugin
1788                     require Koha::FrameworkPlugin;
1789                     my $plugin = Koha::FrameworkPlugin->new({
1790                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
1791                         item_style => 1,
1792                     });
1793                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1794                     $plugin->build( $pars );
1795                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1796                         $defaultvalue = $field->subfield($subfield) || q{};
1797                     }
1798                     if( !$plugin->errstr ) {
1799                         #TODO Move html to template; see report 12176/13397
1800                         my $tab= $plugin->noclick? '-1': '';
1801                         my $class= $plugin->noclick? ' disabled': '';
1802                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
1803                         $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
1804                     } else {
1805                         warn $plugin->errstr;
1806                         $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
1807                     }
1808                 }
1809                 elsif ( $tag eq '' ) {       # it's an hidden field
1810                     $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1811                 }
1812                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
1813                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1814                 }
1815                 elsif ( length($defaultvalue) > 100
1816                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1817                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
1818                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
1819                                   500 <= $tag && $tag < 600                     )
1820                           ) {
1821                     # oversize field (textarea)
1822                     $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1823                 } else {
1824                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1825                 }
1826                 push( @loop_data, \%subfield_data );
1827             }
1828         }
1829     }
1830     my $itemnumber;
1831     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1832         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1833     }
1834     return {
1835         'itemtagfield'    => $itemtagfield,
1836         'itemtagsubfield' => $itemtagsubfield,
1837         'itemnumber'      => $itemnumber,
1838         'iteminformation' => \@loop_data
1839     };
1840 }
1841
1842 sub ToggleNewStatus {
1843     my ( $params ) = @_;
1844     my @rules = @{ $params->{rules} };
1845     my $report_only = $params->{report_only};
1846
1847     my $dbh = C4::Context->dbh;
1848     my @errors;
1849     my @item_columns = map { "items.$_" } Koha::Items->columns;
1850     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1851     my $report;
1852     for my $rule ( @rules ) {
1853         my $age = $rule->{age};
1854         my $conditions = $rule->{conditions};
1855         my $substitutions = $rule->{substitutions};
1856         foreach ( @$substitutions ) {
1857             ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1858         }
1859         my @params;
1860
1861         my $query = q|
1862             SELECT items.*
1863             FROM items
1864             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1865             WHERE 1
1866         |;
1867         for my $condition ( @$conditions ) {
1868             if (
1869                  grep { $_ eq $condition->{field} } @item_columns
1870               or grep { $_ eq $condition->{field} } @biblioitem_columns
1871             ) {
1872                 if ( $condition->{value} =~ /\|/ ) {
1873                     my @values = split /\|/, $condition->{value};
1874                     $query .= qq| AND $condition->{field} IN (|
1875                         . join( ',', ('?') x scalar @values )
1876                         . q|)|;
1877                     push @params, @values;
1878                 } else {
1879                     $query .= qq| AND $condition->{field} = ?|;
1880                     push @params, $condition->{value};
1881                 }
1882             }
1883         }
1884         if ( defined $age ) {
1885             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1886             push @params, $age;
1887         }
1888         my $sth = $dbh->prepare($query);
1889         $sth->execute( @params );
1890         while ( my $values = $sth->fetchrow_hashref ) {
1891             my $biblionumber = $values->{biblionumber};
1892             my $itemnumber = $values->{itemnumber};
1893             my $item = Koha::Items->find($itemnumber);
1894             for my $substitution ( @$substitutions ) {
1895                 my $field = $substitution->{item_field};
1896                 my $value = $substitution->{value};
1897                 next unless $substitution->{field};
1898                 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1899                 $item->$field($value);
1900                 push @{ $report->{$itemnumber} }, $substitution;
1901             }
1902             $item->store unless $report_only;
1903         }
1904     }
1905
1906     return $report;
1907 }
1908
1909 1;