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