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