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