Bug 23583: Handle OpacHiddenItems with yaml_preference
[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 DateTime::Format::MySQL;
61 use Data::Dumper; # used as part of logging item record changes, not just for
62                   # debugging; so please don't remove this
63
64 use Koha::AuthorisedValues;
65 use Koha::DateUtils qw(dt_from_string);
66 use Koha::Database;
67
68 use Koha::Biblioitems;
69 use Koha::Items;
70 use Koha::ItemTypes;
71 use Koha::SearchEngine;
72 use Koha::SearchEngine::Indexer;
73 use Koha::SearchEngine::Search;
74 use Koha::Libraries;
75
76 =head1 NAME
77
78 C4::Items - item management functions
79
80 =head1 DESCRIPTION
81
82 This module contains an API for manipulating item 
83 records in Koha, and is used by cataloguing, circulation,
84 acquisitions, and serials management.
85
86 # FIXME This POD is not up-to-date
87 A Koha item record is stored in two places: the
88 items table and embedded in a MARC tag in the XML
89 version of the associated bib record in C<biblioitems.marcxml>.
90 This is done to allow the item information to be readily
91 indexed (e.g., by Zebra), but means that each item
92 modification transaction must keep the items table
93 and the MARC XML in sync at all times.
94
95 The items table will be considered authoritative.  In other
96 words, if there is ever a discrepancy between the items
97 table and the MARC XML, the items table should be considered
98 accurate.
99
100 =head1 HISTORICAL NOTE
101
102 Most of the functions in C<C4::Items> were originally in
103 the C<C4::Biblio> module.
104
105 =head1 CORE EXPORTED FUNCTIONS
106
107 The following functions are meant for use by users
108 of C<C4::Items>
109
110 =cut
111
112 =head2 CartToShelf
113
114   CartToShelf($itemnumber);
115
116 Set the current shelving location of the item record
117 to its stored permanent shelving location.  This is
118 primarily used to indicate when an item whose current
119 location is a special processing ('PROC') or shelving cart
120 ('CART') location is back in the stacks.
121
122 =cut
123
124 sub CartToShelf {
125     my ( $itemnumber ) = @_;
126
127     unless ( $itemnumber ) {
128         croak "FAILED CartToShelf() - no itemnumber supplied";
129     }
130
131     my $item = Koha::Items->find($itemnumber);
132     if ( $item->location eq 'CART' ) {
133         $item->location($item->permanent_location)->store;
134     }
135 }
136
137 =head2 AddItemFromMarc
138
139   my ($biblionumber, $biblioitemnumber, $itemnumber) 
140       = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
141
142 Given a MARC::Record object containing an embedded item
143 record and a biblionumber, create a new item record.
144
145 The final optional parameter, C<$params>, expected to contain
146 'skip_record_index' key, which relayed down to Koha::Item/store,
147 there it prevents calling of index_records,
148 which takes most of the time in batch adds/deletes: index_records
149 to be called later in C<additem.pl> after the whole loop.
150
151 $params:
152     skip_record_index => 1|0
153
154 =cut
155
156 sub AddItemFromMarc {
157     my $source_item_marc = shift;
158     my $biblionumber     = shift;
159     my $params           = @_ ? shift : {};
160
161     my $dbh = C4::Context->dbh;
162
163     # parse item hash from MARC
164     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
165     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
166
167     my $localitemmarc = MARC::Record->new;
168     $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
169
170     my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
171     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
172     $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
173     $item_values->{biblionumber} = $biblionumber;
174     $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
175     $item_values->{cn_sort}   = delete $item_values->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
176     my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
177     return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
178 }
179
180 =head2 AddItemBatchFromMarc
181
182   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
183              $biblionumber, $biblioitemnumber, $frameworkcode);
184
185 Efficiently create item records from a MARC biblio record with
186 embedded item fields.  This routine is suitable for batch jobs.
187
188 This API assumes that the bib record has already been
189 saved to the C<biblio> and C<biblioitems> tables.  It does
190 not expect that C<biblio_metadata.metadata> is populated, but it
191 will do so via a call to ModBibiloMarc.
192
193 The goal of this API is to have a similar effect to using AddBiblio
194 and AddItems in succession, but without inefficient repeated
195 parsing of the MARC XML bib record.
196
197 This function returns an arrayref of new itemsnumbers and an arrayref of item
198 errors encountered during the processing.  Each entry in the errors
199 list is a hashref containing the following keys:
200
201 =over
202
203 =item item_sequence
204
205 Sequence number of original item tag in the MARC record.
206
207 =item item_barcode
208
209 Item barcode, provide to assist in the construction of
210 useful error messages.
211
212 =item error_code
213
214 Code representing the error condition.  Can be 'duplicate_barcode',
215 'invalid_homebranch', or 'invalid_holdingbranch'.
216
217 =item error_information
218
219 Additional information appropriate to the error condition.
220
221 =back
222
223 =cut
224
225 sub AddItemBatchFromMarc {
226     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
227     my @itemnumbers = ();
228     my @errors = ();
229     my $dbh = C4::Context->dbh;
230
231     # We modify the record, so lets work on a clone so we don't change the
232     # original.
233     $record = $record->clone();
234     # loop through the item tags and start creating items
235     my @bad_item_fields = ();
236     my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
237     my $item_sequence_num = 0;
238     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
239         $item_sequence_num++;
240         # we take the item field and stick it into a new
241         # MARC record -- this is required so far because (FIXME)
242         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
243         # and there is no TransformMarcFieldToKoha
244         my $temp_item_marc = MARC::Record->new();
245         $temp_item_marc->append_fields($item_field);
246     
247         # add biblionumber and biblioitemnumber
248         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
249         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
250         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
251         $item->{'biblionumber'} = $biblionumber;
252         $item->{'biblioitemnumber'} = $biblioitemnumber;
253         $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
254         $item->{cn_sort}   = delete $item->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
255
256         # check for duplicate barcode
257         my %item_errors = CheckItemPreSave($item);
258         if (%item_errors) {
259             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
260             push @bad_item_fields, $item_field;
261             next ITEMFIELD;
262         }
263
264         my $item_object = Koha::Item->new($item)->store;
265         push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
266
267         logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
268
269         my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
270         $item_field->replace_with($new_item_marc->field($itemtag));
271     }
272
273     # remove any MARC item fields for rejected items
274     foreach my $item_field (@bad_item_fields) {
275         $record->delete_field($item_field);
276     }
277
278     return (\@itemnumbers, \@errors);
279 }
280
281 =head2 ModItemFromMarc
282
283 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
284
285 The final optional parameter, C<$params>, expected to contain
286 'skip_record_index' key, which relayed down to Koha::Item/store,
287 there it prevents calling of index_records,
288 which takes most of the time in batch adds/deletes: index_records better
289 to be called later in C<additem.pl> after the whole loop.
290
291 $params:
292     skip_record_index => 1|0
293
294 =cut
295
296 sub ModItemFromMarc {
297     my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
298
299     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
300     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
301
302     my $localitemmarc = MARC::Record->new;
303     $localitemmarc->append_fields( $item_marc->field($itemtag) );
304     my $item_object = Koha::Items->find($itemnumber);
305     my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
306
307     # Retrieving the values for the fields that are not linked
308     my @mapped_fields = Koha::MarcSubfieldStructures->search(
309         {
310             frameworkcode => $frameworkcode,
311             kohafield     => { -like => "items.%" }
312         }
313     )->get_column('kohafield');
314     for my $c ( $item_object->_result->result_source->columns ) {
315         next if grep { "items.$c" eq $_ } @mapped_fields;
316         $item->{$c} = $item_object->$c;
317     }
318
319     $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
320     delete $item->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
321     $item->{itemnumber} = $itemnumber;
322     $item->{biblionumber} = $biblionumber;
323
324     my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
325                                                   # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
326     $item_object = $item_object->set_or_blank($item);
327     $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
328
329     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
330     $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
331     $item_object->store({ skip_record_index => $params->{skip_record_index} });
332
333     return $item_object->unblessed;
334 }
335
336 =head2 ModItemTransfer
337
338   ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
339
340 Marks an item as being transferred from one branch to another and records the trigger.
341
342 The last optional parameter allows for passing skip_record_index through to the items store call.
343
344 =cut
345
346 sub ModItemTransfer {
347     my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
348
349     my $dbh = C4::Context->dbh;
350     my $item = Koha::Items->find( $itemnumber );
351
352     # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
353     # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
354     # will have been preceded by a check of branch transfer limits)
355     my $to_library = Koha::Libraries->find($tobranch);
356     my $transfer = $item->request_transfer(
357         {
358             to            => $to_library,
359             reason        => $trigger,
360             ignore_limits => 1,
361             replace       => 1
362         }
363     );
364
365     # Immediately set the item to in transit if it is checked in
366     if ( !$item->checkout ) {
367         $item->holdingbranch($frombranch)->store(
368             {
369                 log_action        => 0,
370                 skip_record_index => $params->{skip_record_index}
371             }
372         );
373         $transfer->transit;
374     }
375
376     return;
377 }
378
379 =head2 ModDateLastSeen
380
381 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
382
383 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
384 C<$itemnumber> is the item number
385 C<$leave_item_lost> determines if a lost item will be found or remain lost
386
387 The last optional parameter allows for passing skip_record_index through to the items store call.
388
389 =cut
390
391 sub ModDateLastSeen {
392     my ( $itemnumber, $leave_item_lost, $params ) = @_;
393
394     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
395
396     my $item = Koha::Items->find($itemnumber);
397     $item->datelastseen($today);
398     $item->itemlost(0) unless $leave_item_lost;
399     $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
400 }
401
402 =head2 CheckItemPreSave
403
404     my $item_ref = TransformMarcToKoha($marc, 'items');
405     # do stuff
406     my %errors = CheckItemPreSave($item_ref);
407     if (exists $errors{'duplicate_barcode'}) {
408         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
409     } elsif (exists $errors{'invalid_homebranch'}) {
410         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
411     } elsif (exists $errors{'invalid_holdingbranch'}) {
412         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
413     } else {
414         print "item is OK";
415     }
416
417 Given a hashref containing item fields, determine if it can be
418 inserted or updated in the database.  Specifically, checks for
419 database integrity issues, and returns a hash containing any
420 of the following keys, if applicable.
421
422 =over 2
423
424 =item duplicate_barcode
425
426 Barcode, if it duplicates one already found in the database.
427
428 =item invalid_homebranch
429
430 Home branch, if not defined in branches table.
431
432 =item invalid_holdingbranch
433
434 Holding branch, if not defined in branches table.
435
436 =back
437
438 This function does NOT implement any policy-related checks,
439 e.g., whether current operator is allowed to save an
440 item that has a given branch code.
441
442 =cut
443
444 sub CheckItemPreSave {
445     my $item_ref = shift;
446
447     my %errors = ();
448
449     # check for duplicate barcode
450     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
451         my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
452         if ($existing_item) {
453             if (!exists $item_ref->{'itemnumber'}                       # new item
454                 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
455                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
456             }
457         }
458     }
459
460     # check for valid home branch
461     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
462         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
463         unless (defined $home_library) {
464             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
465         }
466     }
467
468     # check for valid holding branch
469     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
470         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
471         unless (defined $holding_library) {
472             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
473         }
474     }
475
476     return %errors;
477
478 }
479
480 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
481
482 The following functions provide various ways of 
483 getting an item record, a set of item records, or
484 lists of authorized values for certain item fields.
485
486 =cut
487
488 =head2 GetItemsForInventory
489
490 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
491   minlocation  => $minlocation,
492   maxlocation  => $maxlocation,
493   location     => $location,
494   ignoreissued => $ignoreissued,
495   datelastseen => $datelastseen,
496   branchcode   => $branchcode,
497   branch       => $branch,
498   offset       => $offset,
499   size         => $size,
500   statushash   => $statushash,
501   itemtypes    => \@itemsarray,
502 } );
503
504 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
505
506 The sub returns a reference to a list of hashes, each containing
507 itemnumber, author, title, barcode, item callnumber, and date last
508 seen. It is ordered by callnumber then title.
509
510 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
511 the datelastseen can be used to specify that you want to see items not seen since a past date only.
512 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
513 $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.
514
515 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
516
517 =cut
518
519 sub GetItemsForInventory {
520     my ( $parameters ) = @_;
521     my $minlocation  = $parameters->{'minlocation'}  // '';
522     my $maxlocation  = $parameters->{'maxlocation'}  // '';
523     my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
524     my $location     = $parameters->{'location'}     // '';
525     my $itemtype     = $parameters->{'itemtype'}     // '';
526     my $ignoreissued = $parameters->{'ignoreissued'} // '';
527     my $datelastseen = $parameters->{'datelastseen'} // '';
528     my $branchcode   = $parameters->{'branchcode'}   // '';
529     my $branch       = $parameters->{'branch'}       // '';
530     my $offset       = $parameters->{'offset'}       // '';
531     my $size         = $parameters->{'size'}         // '';
532     my $statushash   = $parameters->{'statushash'}   // '';
533     my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
534     my $itemtypes    = $parameters->{'itemtypes'}    || [];
535
536     my $dbh = C4::Context->dbh;
537     my ( @bind_params, @where_strings );
538
539     my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
540     my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
541
542     my $select_columns = q{
543         SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
544     };
545     my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
546     my $query = q{
547         FROM items
548         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
549         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
550     };
551     if ($statushash){
552         for my $authvfield (keys %$statushash){
553             if ( scalar @{$statushash->{$authvfield}} > 0 ){
554                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
555                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
556             }
557         }
558     }
559
560     if ($minlocation) {
561         push @where_strings, 'items.cn_sort >= ?';
562         push @bind_params, $min_cnsort;
563     }
564
565     if ($maxlocation) {
566         push @where_strings, 'items.cn_sort <= ?';
567         push @bind_params, $max_cnsort;
568     }
569
570     if ($datelastseen) {
571         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
572         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
573         push @bind_params, $datelastseen;
574     }
575
576     if ( $location ) {
577         push @where_strings, 'items.location = ?';
578         push @bind_params, $location;
579     }
580
581     if ( $branchcode ) {
582         if($branch eq "homebranch"){
583         push @where_strings, 'items.homebranch = ?';
584         }else{
585             push @where_strings, 'items.holdingbranch = ?';
586         }
587         push @bind_params, $branchcode;
588     }
589
590     if ( $itemtype ) {
591         push @where_strings, 'biblioitems.itemtype = ?';
592         push @bind_params, $itemtype;
593     }
594     if ( $ignoreissued) {
595         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
596         push @where_strings, 'issues.date_due IS NULL';
597     }
598
599     if ( $ignore_waiting_holds ) {
600         $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
601         push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
602     }
603
604     if ( @$itemtypes ) {
605         my $itemtypes_str = join ', ', @$itemtypes;
606         push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
607     }
608
609     if ( @where_strings ) {
610         $query .= 'WHERE ';
611         $query .= join ' AND ', @where_strings;
612     }
613     my $count_query = $select_count . $query;
614     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
615     $query .= " LIMIT $offset, $size" if ($offset and $size);
616     $query = $select_columns . $query;
617     my $sth = $dbh->prepare($query);
618     $sth->execute( @bind_params );
619
620     my @results = ();
621     my $tmpresults = $sth->fetchall_arrayref({});
622     $sth = $dbh->prepare( $count_query );
623     $sth->execute( @bind_params );
624     my ($iTotalRecords) = $sth->fetchrow_array();
625
626     my @avs = Koha::AuthorisedValues->search(
627         {   'marc_subfield_structures.kohafield' => { '>' => '' },
628             'me.authorised_value'                => { '>' => '' },
629         },
630         {   join     => { category => 'marc_subfield_structures' },
631             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
632             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
633             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
634         }
635     );
636
637     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
638
639     foreach my $row (@$tmpresults) {
640
641         # Auth values
642         foreach (keys %$row) {
643             if (
644                 defined(
645                     $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
646                 )
647             ) {
648                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
649             }
650         }
651         push @results, $row;
652     }
653
654     return (\@results, $iTotalRecords);
655 }
656
657 =head2 GetItemsInfo
658
659   @results = GetItemsInfo($biblionumber);
660
661 Returns information about items with the given biblionumber.
662
663 C<GetItemsInfo> returns a list of references-to-hash. Each element
664 contains a number of keys. Most of them are attributes from the
665 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
666 Koha database. Other keys include:
667
668 =over 2
669
670 =item C<$data-E<gt>{branchname}>
671
672 The name (not the code) of the branch to which the book belongs.
673
674 =item C<$data-E<gt>{datelastseen}>
675
676 This is simply C<items.datelastseen>, except that while the date is
677 stored in YYYY-MM-DD format in the database, here it is converted to
678 DD/MM/YYYY format. A NULL date is returned as C<//>.
679
680 =item C<$data-E<gt>{datedue}>
681
682 =item C<$data-E<gt>{class}>
683
684 This is the concatenation of C<biblioitems.classification>, the book's
685 Dewey code, and C<biblioitems.subclass>.
686
687 =item C<$data-E<gt>{ocount}>
688
689 I think this is the number of copies of the book available.
690
691 =item C<$data-E<gt>{order}>
692
693 If this is set, it is set to C<One Order>.
694
695 =back
696
697 =cut
698
699 sub GetItemsInfo {
700     my ( $biblionumber ) = @_;
701     my $dbh   = C4::Context->dbh;
702     require C4::Languages;
703     my $language = C4::Languages::getlanguage();
704     my $query = "
705     SELECT items.*,
706            biblio.*,
707            biblioitems.volume,
708            biblioitems.number,
709            biblioitems.itemtype,
710            biblioitems.isbn,
711            biblioitems.issn,
712            biblioitems.publicationyear,
713            biblioitems.publishercode,
714            biblioitems.volumedate,
715            biblioitems.volumedesc,
716            biblioitems.lccn,
717            biblioitems.url,
718            items.notforloan as itemnotforloan,
719            issues.borrowernumber,
720            issues.date_due as datedue,
721            issues.onsite_checkout,
722            borrowers.cardnumber,
723            borrowers.surname,
724            borrowers.firstname,
725            borrowers.branchcode as bcode,
726            serial.serialseq,
727            serial.publisheddate,
728            itemtypes.description,
729            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
730            itemtypes.notforloan as notforloan_per_itemtype,
731            holding.branchurl,
732            holding.branchcode,
733            holding.branchname,
734            holding.opac_info as holding_branch_opac_info,
735            home.opac_info as home_branch_opac_info,
736            IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
737      FROM items
738      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
739      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
740      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
741      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
742      LEFT JOIN issues USING (itemnumber)
743      LEFT JOIN borrowers USING (borrowernumber)
744      LEFT JOIN serialitems USING (itemnumber)
745      LEFT JOIN serial USING (serialid)
746      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
747      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
748     $query .= q|
749     LEFT JOIN tmp_holdsqueue USING (itemnumber)
750     LEFT JOIN localization ON itemtypes.itemtype = localization.code
751         AND localization.entity = 'itemtypes'
752         AND localization.lang = ?
753     |;
754
755     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
756     my $sth = $dbh->prepare($query);
757     $sth->execute($language, $biblionumber);
758     my $i = 0;
759     my @results;
760     my $serial;
761
762     my $userenv = C4::Context->userenv;
763     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
764     while ( my $data = $sth->fetchrow_hashref ) {
765         if ( $data->{borrowernumber} && $want_not_same_branch) {
766             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
767         }
768
769         $serial ||= $data->{'serial'};
770
771         my $descriptions;
772         # get notforloan complete status if applicable
773         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
774         $data->{notforloanvalue}     = $descriptions->{lib} // '';
775         $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
776
777         # get restricted status and description if applicable
778         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
779         $data->{restrictedvalue}     = $descriptions->{lib} // '';
780         $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
781
782         # my stack procedures
783         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
784         $data->{stack}          = $descriptions->{lib} // '';
785
786         # Find the last 3 people who borrowed this item.
787         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
788                                     WHERE itemnumber = ?
789                                     AND old_issues.borrowernumber = borrowers.borrowernumber
790                                     ORDER BY returndate DESC
791                                     LIMIT 3");
792         $sth2->execute($data->{'itemnumber'});
793         my $ii = 0;
794         while (my $data2 = $sth2->fetchrow_hashref()) {
795             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
796             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
797             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
798             $ii++;
799         }
800
801         $results[$i] = $data;
802         $i++;
803     }
804
805     return $serial
806         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
807         : @results;
808 }
809
810 =head2 GetItemsLocationInfo
811
812   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
813
814 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
815
816 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
817
818 =over 2
819
820 =item C<$data-E<gt>{homebranch}>
821
822 Branch Name of the item's homebranch
823
824 =item C<$data-E<gt>{holdingbranch}>
825
826 Branch Name of the item's holdingbranch
827
828 =item C<$data-E<gt>{location}>
829
830 Item's shelving location code
831
832 =item C<$data-E<gt>{location_intranet}>
833
834 The intranet description for the Shelving Location as set in authorised_values 'LOC'
835
836 =item C<$data-E<gt>{location_opac}>
837
838 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
839 description is set.
840
841 =item C<$data-E<gt>{itemcallnumber}>
842
843 Item's itemcallnumber
844
845 =item C<$data-E<gt>{cn_sort}>
846
847 Item's call number normalized for sorting
848
849 =back
850
851 =cut
852
853 sub GetItemsLocationInfo {
854         my $biblionumber = shift;
855         my @results;
856
857         my $dbh = C4::Context->dbh;
858         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
859                             location, itemcallnumber, cn_sort
860                      FROM items, branches as a, branches as b
861                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
862                      AND biblionumber = ?
863                      ORDER BY cn_sort ASC";
864         my $sth = $dbh->prepare($query);
865         $sth->execute($biblionumber);
866
867         while ( my $data = $sth->fetchrow_hashref ) {
868              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
869              $av = $av->count ? $av->next : undef;
870              $data->{location_intranet} = $av ? $av->lib : '';
871              $data->{location_opac}     = $av ? $av->opac_description : '';
872              push @results, $data;
873         }
874         return @results;
875 }
876
877 =head2 GetHostItemsInfo
878
879     $hostiteminfo = GetHostItemsInfo($hostfield);
880     Returns the iteminfo for items linked to records via a host field
881
882 =cut
883
884 sub GetHostItemsInfo {
885     my ($record) = @_;
886     my @returnitemsInfo;
887
888     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
889         return @returnitemsInfo;
890     }
891
892     my @fields;
893     if( C4::Context->preference('marcflavour') eq 'MARC21' ||
894       C4::Context->preference('marcflavour') eq 'NORMARC') {
895         @fields = $record->field('773');
896     } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
897         @fields = $record->field('461');
898     }
899
900     foreach my $hostfield ( @fields ) {
901         my $hostbiblionumber = $hostfield->subfield("0");
902         my $linkeditemnumber = $hostfield->subfield("9");
903         my @hostitemInfos = GetItemsInfo($hostbiblionumber);
904         foreach my $hostitemInfo (@hostitemInfos) {
905             if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
906                 push @returnitemsInfo, $hostitemInfo;
907                 last;
908             }
909         }
910     }
911     return @returnitemsInfo;
912 }
913
914 =head2 get_hostitemnumbers_of
915
916   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
917
918 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
919
920 Return a reference on a hash where key is a biblionumber and values are
921 references on array of itemnumbers.
922
923 =cut
924
925
926 sub get_hostitemnumbers_of {
927     my ($biblionumber) = @_;
928
929     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
930         return ();
931     }
932
933     my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
934     return unless $marcrecord;
935
936     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
937
938     my $marcflavor = C4::Context->preference('marcflavour');
939     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
940         $tag      = '773';
941         $biblio_s = '0';
942         $item_s   = '9';
943     }
944     elsif ( $marcflavor eq 'UNIMARC' ) {
945         $tag      = '461';
946         $biblio_s = '0';
947         $item_s   = '9';
948     }
949
950     foreach my $hostfield ( $marcrecord->field($tag) ) {
951         my $hostbiblionumber = $hostfield->subfield($biblio_s);
952         next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
953         my $linkeditemnumber = $hostfield->subfield($item_s);
954         if ( ! $linkeditemnumber ) {
955             warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
956             next;
957         }
958         my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
959         push @returnhostitemnumbers, $linkeditemnumber
960           if $is_from_biblio;
961     }
962
963     return @returnhostitemnumbers;
964 }
965
966 =head2 GetHiddenItemnumbers
967
968     my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
969
970 Given a list of items it checks which should be hidden from the OPAC given
971 the current configuration. Returns a list of itemnumbers corresponding to
972 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
973 to be excluded
974
975 =cut
976
977 sub GetHiddenItemnumbers {
978     my $params = shift;
979     my $items = $params->{items};
980     if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
981         foreach my $except (split(/\|/, $exceptions)){
982             if ($params->{'borcat'} eq $except){
983                 return; # we don't hide anything for this borrower category
984             }
985         }
986     }
987     my @resultitems;
988
989     my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
990
991     return
992         unless $hidingrules;
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     # Pick the default location from NewItemsDefaultLocation
1598     if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1599         $defaultvalues //= {};
1600         $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1601     }
1602
1603     # return nothing if we don't have found an existing framework.
1604     return q{} unless $tagslib;
1605     my $itemrecord;
1606     if ($itemnum) {
1607         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1608     }
1609     my @loop_data;
1610
1611     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1612     my $query = qq{
1613         SELECT authorised_value,lib FROM authorised_values
1614     };
1615     $query .= qq{
1616         LEFT JOIN authorised_values_branches ON ( id = av_id )
1617     } if $branch_limit;
1618     $query .= qq{
1619         WHERE category = ?
1620     };
1621     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1622     $query .= qq{ ORDER BY lib};
1623     my $authorised_values_sth = $dbh->prepare( $query );
1624     foreach my $tag ( sort keys %{$tagslib} ) {
1625         if ( $tag ne '' ) {
1626
1627             # loop through each subfield
1628             my $cntsubf;
1629             foreach my $subfield (
1630                 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1631                 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1632                 values %{ $tagslib->{$tag} } )
1633             {
1634                 next unless ( $subfield->{'tab'} );
1635                 next if ( $subfield->{'tab'} ne "10" );
1636                 my %subfield_data;
1637                 $subfield_data{tag}           = $tag;
1638                 $subfield_data{subfield}      = $subfield->{subfield};
1639                 $subfield_data{countsubfield} = $cntsubf++;
1640                 $subfield_data{kohafield}     = $subfield->{kohafield};
1641                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1642
1643                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1644                 $subfield_data{marc_lib}   = $subfield->{lib};
1645                 $subfield_data{mandatory}  = $subfield->{mandatory};
1646                 $subfield_data{repeatable} = $subfield->{repeatable};
1647                 $subfield_data{hidden}     = "display:none"
1648                   if ( ( $subfield->{hidden} > 4 )
1649                     || ( $subfield->{hidden} < -4 ) );
1650                 my ( $x, $defaultvalue );
1651                 if ($itemrecord) {
1652                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1653                 }
1654                 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1655                 if ( !defined $defaultvalue ) {
1656                     $defaultvalue = q||;
1657                 } else {
1658                     $defaultvalue =~ s/"/&quot;/g;
1659                     # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1660                     my $today_dt = dt_from_string;
1661                     my $year     = $today_dt->strftime('%Y');
1662                     my $shortyear     = $today_dt->strftime('%y');
1663                     my $month    = $today_dt->strftime('%m');
1664                     my $day      = $today_dt->strftime('%d');
1665                     $defaultvalue =~ s/<<YYYY>>/$year/g;
1666                     $defaultvalue =~ s/<<YY>>/$shortyear/g;
1667                     $defaultvalue =~ s/<<MM>>/$month/g;
1668                     $defaultvalue =~ s/<<DD>>/$day/g;
1669
1670                     # And <<USER>> with surname (?)
1671                     my $username =
1672                       (   C4::Context->userenv
1673                         ? C4::Context->userenv->{'surname'}
1674                         : "superlibrarian" );
1675                     $defaultvalue =~ s/<<USER>>/$username/g;
1676                 }
1677
1678                 my $maxlength = $subfield->{maxlength};
1679
1680                 # search for itemcallnumber if applicable
1681                 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1682                     && C4::Context->preference('itemcallnumber') && $itemrecord) {
1683                     foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1684                         my $CNtag      = substr( $itemcn_pref, 0, 3 );
1685                         next unless my $field = $itemrecord->field($CNtag);
1686                         my $CNsubfields = substr( $itemcn_pref, 3 );
1687                         $defaultvalue = $field->as_string( $CNsubfields, ' ');
1688                         last if $defaultvalue;
1689                     }
1690                 }
1691                 if (   $subfield->{kohafield} eq 'items.itemcallnumber'
1692                     && $defaultvalues
1693                     && $defaultvalues->{'callnumber'} ) {
1694                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1695                         # if the item record exists, only use default value if the item has no callnumber
1696                         $defaultvalue = $defaultvalues->{callnumber};
1697                     } elsif ( !$itemrecord and $defaultvalues ) {
1698                         # if the item record *doesn't* exists, always use the default value
1699                         $defaultvalue = $defaultvalues->{callnumber};
1700                     }
1701                 }
1702                 if (   ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1703                     && $defaultvalues
1704                     && $defaultvalues->{'branchcode'} ) {
1705                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1706                         $defaultvalue = $defaultvalues->{branchcode};
1707                     }
1708                 }
1709                 if (   ( $subfield->{kohafield} eq 'items.location' )
1710                     && $defaultvalues
1711                     && $defaultvalues->{'location'} ) {
1712
1713                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1714                         # if the item record exists, only use default value if the item has no locationr
1715                         $defaultvalue = $defaultvalues->{location};
1716                     } elsif ( !$itemrecord and $defaultvalues ) {
1717                         # if the item record *doesn't* exists, always use the default value
1718                         $defaultvalue = $defaultvalues->{location};
1719                     }
1720                 }
1721                 if ( $subfield->{authorised_value} ) {
1722                     my @authorised_values;
1723                     my %authorised_lib;
1724
1725                     # builds list, depending on authorised value...
1726                     #---- branch
1727                     if ( $subfield->{'authorised_value'} eq "branches" ) {
1728                         if (   ( C4::Context->preference("IndependentBranches") )
1729                             && !C4::Context->IsSuperLibrarian() ) {
1730                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1731                             $sth->execute( C4::Context->userenv->{branch} );
1732                             push @authorised_values, ""
1733                               unless ( $subfield->{mandatory} );
1734                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1735                                 push @authorised_values, $branchcode;
1736                                 $authorised_lib{$branchcode} = $branchname;
1737                             }
1738                         } else {
1739                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1740                             $sth->execute;
1741                             push @authorised_values, ""
1742                               unless ( $subfield->{mandatory} );
1743                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1744                                 push @authorised_values, $branchcode;
1745                                 $authorised_lib{$branchcode} = $branchname;
1746                             }
1747                         }
1748
1749                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1750                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1751                             $defaultvalue = $defaultvalues->{branchcode};
1752                         }
1753
1754                         #----- itemtypes
1755                     } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1756                         my $itemtypes = Koha::ItemTypes->search_with_localization;
1757                         push @authorised_values, "";
1758                         while ( my $itemtype = $itemtypes->next ) {
1759                             push @authorised_values, $itemtype->itemtype;
1760                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1761                         }
1762                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1763                             $defaultvalue = $defaultvalues->{'itemtype'};
1764                         }
1765
1766                         #---- class_sources
1767                     } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1768                         push @authorised_values, "";
1769
1770                         my $class_sources = GetClassSources();
1771                         my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1772
1773                         foreach my $class_source (sort keys %$class_sources) {
1774                             next unless $class_sources->{$class_source}->{'used'} or
1775                                         ($class_source eq $default_source);
1776                             push @authorised_values, $class_source;
1777                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1778                         }
1779
1780                         $defaultvalue = $default_source;
1781
1782                         #---- "true" authorised value
1783                     } else {
1784                         $authorised_values_sth->execute(
1785                             $subfield->{authorised_value},
1786                             $branch_limit ? $branch_limit : ()
1787                         );
1788                         push @authorised_values, "";
1789                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1790                             push @authorised_values, $value;
1791                             $authorised_lib{$value} = $lib;
1792                         }
1793                     }
1794                     $subfield_data{marc_value} = {
1795                         type    => 'select',
1796                         values  => \@authorised_values,
1797                         default => $defaultvalue // q{},
1798                         labels  => \%authorised_lib,
1799                     };
1800                 } elsif ( $subfield->{value_builder} ) {
1801                 # it is a plugin
1802                     require Koha::FrameworkPlugin;
1803                     my $plugin = Koha::FrameworkPlugin->new({
1804                         name => $subfield->{value_builder},
1805                         item_style => 1,
1806                     });
1807                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1808                     $plugin->build( $pars );
1809                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1810                         $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1811                     }
1812                     if( !$plugin->errstr ) {
1813                         #TODO Move html to template; see report 12176/13397
1814                         my $tab= $plugin->noclick? '-1': '';
1815                         my $class= $plugin->noclick? ' disabled': '';
1816                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
1817                         $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;
1818                     } else {
1819                         warn $plugin->errstr;
1820                         $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
1821                     }
1822                 }
1823                 elsif ( $tag eq '' ) {       # it's an hidden field
1824                     $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1825                 }
1826                 elsif ( $subfield->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
1827                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1828                 }
1829                 elsif ( length($defaultvalue) > 100
1830                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1831                                   300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1832                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
1833                                   500 <= $tag && $tag < 600                     )
1834                           ) {
1835                     # oversize field (textarea)
1836                     $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1837                 } else {
1838                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1839                 }
1840                 push( @loop_data, \%subfield_data );
1841             }
1842         }
1843     }
1844     my $itemnumber;
1845     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1846         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1847     }
1848     return {
1849         'itemtagfield'    => $itemtagfield,
1850         'itemtagsubfield' => $itemtagsubfield,
1851         'itemnumber'      => $itemnumber,
1852         'iteminformation' => \@loop_data
1853     };
1854 }
1855
1856 sub ToggleNewStatus {
1857     my ( $params ) = @_;
1858     my @rules = @{ $params->{rules} };
1859     my $report_only = $params->{report_only};
1860
1861     my $dbh = C4::Context->dbh;
1862     my @errors;
1863     my @item_columns = map { "items.$_" } Koha::Items->columns;
1864     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1865     my $report;
1866     for my $rule ( @rules ) {
1867         my $age = $rule->{age};
1868         my $conditions = $rule->{conditions};
1869         my $substitutions = $rule->{substitutions};
1870         foreach ( @$substitutions ) {
1871             ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1872         }
1873         my @params;
1874
1875         my $query = q|
1876             SELECT items.*
1877             FROM items
1878             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1879             WHERE 1
1880         |;
1881         for my $condition ( @$conditions ) {
1882             if (
1883                  grep { $_ eq $condition->{field} } @item_columns
1884               or grep { $_ eq $condition->{field} } @biblioitem_columns
1885             ) {
1886                 if ( $condition->{value} =~ /\|/ ) {
1887                     my @values = split /\|/, $condition->{value};
1888                     $query .= qq| AND $condition->{field} IN (|
1889                         . join( ',', ('?') x scalar @values )
1890                         . q|)|;
1891                     push @params, @values;
1892                 } else {
1893                     $query .= qq| AND $condition->{field} = ?|;
1894                     push @params, $condition->{value};
1895                 }
1896             }
1897         }
1898         if ( defined $age ) {
1899             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1900             push @params, $age;
1901         }
1902         my $sth = $dbh->prepare($query);
1903         $sth->execute( @params );
1904         while ( my $values = $sth->fetchrow_hashref ) {
1905             my $biblionumber = $values->{biblionumber};
1906             my $itemnumber = $values->{itemnumber};
1907             my $item = Koha::Items->find($itemnumber);
1908             for my $substitution ( @$substitutions ) {
1909                 my $field = $substitution->{item_field};
1910                 my $value = $substitution->{value};
1911                 next unless $substitution->{field};
1912                 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1913                 $item->$field($value);
1914                 push @{ $report->{$itemnumber} }, $substitution;
1915             }
1916             $item->store unless $report_only;
1917         }
1918     }
1919
1920     return $report;
1921 }
1922
1923 1;