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