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