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