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