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