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