Bug 17481: Fix incorrect merge of bug 11048 (logout redirection for CAS authentication)
[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 strict;
22 #use warnings; FIXME - Bug 2505
23
24 use Carp;
25 use C4::Context;
26 use C4::Koha;
27 use C4::Biblio;
28 use Koha::DateUtils;
29 use MARC::Record;
30 use C4::ClassSource;
31 use C4::Log;
32 use List::MoreUtils qw/any/;
33 use YAML qw/Load/;
34 use DateTime::Format::MySQL;
35 use Data::Dumper; # used as part of logging item record changes, not just for
36                   # debugging; so please don't remove this
37 use Koha::DateUtils qw/dt_from_string/;
38
39 use Koha::Database;
40
41 use vars qw($VERSION @ISA @EXPORT);
42
43 BEGIN {
44     $VERSION = 3.07.00.049;
45
46         require Exporter;
47     @ISA = qw( Exporter );
48
49     # function exports
50     @EXPORT = qw(
51         GetItem
52         AddItemFromMarc
53         AddItem
54         AddItemBatchFromMarc
55         ModItemFromMarc
56     Item2Marc
57         ModItem
58         ModDateLastSeen
59         ModItemTransfer
60         DelItem
61     
62         CheckItemPreSave
63     
64         GetItemStatus
65         GetItemLocation
66         GetLostItems
67         GetItemsForInventory
68         GetItemsCount
69         GetItemInfosOf
70         GetItemsByBiblioitemnumber
71         GetItemsInfo
72         GetItemsLocationInfo
73         GetHostItemsInfo
74         GetItemnumbersForBiblio
75         get_itemnumbers_of
76         get_hostitemnumbers_of
77         GetItemnumberFromBarcode
78         GetBarcodeFromItemnumber
79         GetHiddenItemnumbers
80         DelItemCheck
81     MoveItemFromBiblio
82     GetLatestAcquisitions
83
84         CartToShelf
85         ShelfToCart
86
87         GetAnalyticsCount
88         GetItemHolds
89
90         SearchItemsByField
91         SearchItems
92
93         PrepareItemrecordDisplay
94
95     );
96 }
97
98 =head1 NAME
99
100 C4::Items - item management functions
101
102 =head1 DESCRIPTION
103
104 This module contains an API for manipulating item 
105 records in Koha, and is used by cataloguing, circulation,
106 acquisitions, and serials management.
107
108 A Koha item record is stored in two places: the
109 items table and embedded in a MARC tag in the XML
110 version of the associated bib record in C<biblioitems.marcxml>.
111 This is done to allow the item information to be readily
112 indexed (e.g., by Zebra), but means that each item
113 modification transaction must keep the items table
114 and the MARC XML in sync at all times.
115
116 Consequently, all code that creates, modifies, or deletes
117 item records B<must> use an appropriate function from 
118 C<C4::Items>.  If no existing function is suitable, it is
119 better to add one to C<C4::Items> than to use add
120 one-off SQL statements to add or modify items.
121
122 The items table will be considered authoritative.  In other
123 words, if there is ever a discrepancy between the items
124 table and the MARC XML, the items table should be considered
125 accurate.
126
127 =head1 HISTORICAL NOTE
128
129 Most of the functions in C<C4::Items> were originally in
130 the C<C4::Biblio> module.
131
132 =head1 CORE EXPORTED FUNCTIONS
133
134 The following functions are meant for use by users
135 of C<C4::Items>
136
137 =cut
138
139 =head2 GetItem
140
141   $item = GetItem($itemnumber,$barcode,$serial);
142
143 Return item information, for a given itemnumber or barcode.
144 The return value is a hashref mapping item column
145 names to values.  If C<$serial> is true, include serial publication data.
146
147 =cut
148
149 sub GetItem {
150     my ($itemnumber,$barcode, $serial) = @_;
151     my $dbh = C4::Context->dbh;
152         my $data;
153
154     if ($itemnumber) {
155         my $sth = $dbh->prepare("
156             SELECT * FROM items 
157             WHERE itemnumber = ?");
158         $sth->execute($itemnumber);
159         $data = $sth->fetchrow_hashref;
160     } else {
161         my $sth = $dbh->prepare("
162             SELECT * FROM items 
163             WHERE barcode = ?"
164             );
165         $sth->execute($barcode);                
166         $data = $sth->fetchrow_hashref;
167     }
168
169     return unless ( $data );
170
171     if ( $serial) {      
172     my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
173         $ssth->execute($data->{'itemnumber'}) ;
174         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
175     }
176         #if we don't have an items.itype, use biblioitems.itemtype.
177     # FIXME this should respect the itypes systempreference
178     # if (C4::Context->preference('item-level_itypes')) {
179         if( ! $data->{'itype'} ) {
180                 my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems  WHERE biblionumber = ?");
181                 $sth->execute($data->{'biblionumber'});
182                 ($data->{'itype'}) = $sth->fetchrow_array;
183         }
184     return $data;
185 }    # sub GetItem
186
187 =head2 CartToShelf
188
189   CartToShelf($itemnumber);
190
191 Set the current shelving location of the item record
192 to its stored permanent shelving location.  This is
193 primarily used to indicate when an item whose current
194 location is a special processing ('PROC') or shelving cart
195 ('CART') location is back in the stacks.
196
197 =cut
198
199 sub CartToShelf {
200     my ( $itemnumber ) = @_;
201
202     unless ( $itemnumber ) {
203         croak "FAILED CartToShelf() - no itemnumber supplied";
204     }
205
206     my $item = GetItem($itemnumber);
207     if ( $item->{location} eq 'CART' ) {
208         $item->{location} = $item->{permanent_location};
209         ModItem($item, undef, $itemnumber);
210     }
211 }
212
213 =head2 ShelfToCart
214
215   ShelfToCart($itemnumber);
216
217 Set the current shelving location of the item
218 to shelving cart ('CART').
219
220 =cut
221
222 sub ShelfToCart {
223     my ( $itemnumber ) = @_;
224
225     unless ( $itemnumber ) {
226         croak "FAILED ShelfToCart() - no itemnumber supplied";
227     }
228
229     my $item = GetItem($itemnumber);
230     $item->{'location'} = 'CART';
231     ModItem($item, undef, $itemnumber);
232 }
233
234 =head2 AddItemFromMarc
235
236   my ($biblionumber, $biblioitemnumber, $itemnumber) 
237       = AddItemFromMarc($source_item_marc, $biblionumber);
238
239 Given a MARC::Record object containing an embedded item
240 record and a biblionumber, create a new item record.
241
242 =cut
243
244 sub AddItemFromMarc {
245     my ( $source_item_marc, $biblionumber ) = @_;
246     my $dbh = C4::Context->dbh;
247
248     # parse item hash from MARC
249     my $frameworkcode = GetFrameworkCode( $biblionumber );
250         my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
251         
252         my $localitemmarc=MARC::Record->new;
253         $localitemmarc->append_fields($source_item_marc->field($itemtag));
254     my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode ,'items');
255     my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
256     return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
257 }
258
259 =head2 AddItem
260
261   my ($biblionumber, $biblioitemnumber, $itemnumber) 
262       = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
263
264 Given a hash containing item column names as keys,
265 create a new Koha item record.
266
267 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
268 do not need to be supplied for general use; they exist
269 simply to allow them to be picked up from AddItemFromMarc.
270
271 The final optional parameter, C<$unlinked_item_subfields>, contains
272 an arrayref containing subfields present in the original MARC
273 representation of the item (e.g., from the item editor) that are
274 not mapped to C<items> columns directly but should instead
275 be stored in C<items.more_subfields_xml> and included in 
276 the biblio items tag for display and indexing.
277
278 =cut
279
280 sub AddItem {
281     my $item         = shift;
282     my $biblionumber = shift;
283
284     my $dbh           = @_ ? shift : C4::Context->dbh;
285     my $frameworkcode = @_ ? shift : GetFrameworkCode($biblionumber);
286     my $unlinked_item_subfields;
287     if (@_) {
288         $unlinked_item_subfields = shift;
289     }
290
291     # needs old biblionumber and biblioitemnumber
292     $item->{'biblionumber'} = $biblionumber;
293     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
294     $sth->execute( $item->{'biblionumber'} );
295     ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
296
297     _set_defaults_for_add($item);
298     _set_derived_columns_for_add($item);
299     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
300
301     # FIXME - checks here
302     unless ( $item->{itype} ) {    # default to biblioitem.itemtype if no itype
303         my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
304         $itype_sth->execute( $item->{'biblionumber'} );
305         ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
306     }
307
308     my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
309     return if $error;
310
311     $item->{'itemnumber'} = $itemnumber;
312
313     ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
314
315     logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
316       if C4::Context->preference("CataloguingLog");
317
318     return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
319 }
320
321 =head2 AddItemBatchFromMarc
322
323   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
324              $biblionumber, $biblioitemnumber, $frameworkcode);
325
326 Efficiently create item records from a MARC biblio record with
327 embedded item fields.  This routine is suitable for batch jobs.
328
329 This API assumes that the bib record has already been
330 saved to the C<biblio> and C<biblioitems> tables.  It does
331 not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
332 are populated, but it will do so via a call to ModBibiloMarc.
333
334 The goal of this API is to have a similar effect to using AddBiblio
335 and AddItems in succession, but without inefficient repeated
336 parsing of the MARC XML bib record.
337
338 This function returns an arrayref of new itemsnumbers and an arrayref of item
339 errors encountered during the processing.  Each entry in the errors
340 list is a hashref containing the following keys:
341
342 =over
343
344 =item item_sequence
345
346 Sequence number of original item tag in the MARC record.
347
348 =item item_barcode
349
350 Item barcode, provide to assist in the construction of
351 useful error messages.
352
353 =item error_code
354
355 Code representing the error condition.  Can be 'duplicate_barcode',
356 'invalid_homebranch', or 'invalid_holdingbranch'.
357
358 =item error_information
359
360 Additional information appropriate to the error condition.
361
362 =back
363
364 =cut
365
366 sub AddItemBatchFromMarc {
367     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
368     my $error;
369     my @itemnumbers = ();
370     my @errors = ();
371     my $dbh = C4::Context->dbh;
372
373     # We modify the record, so lets work on a clone so we don't change the
374     # original.
375     $record = $record->clone();
376     # loop through the item tags and start creating items
377     my @bad_item_fields = ();
378     my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
379     my $item_sequence_num = 0;
380     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
381         $item_sequence_num++;
382         # we take the item field and stick it into a new
383         # MARC record -- this is required so far because (FIXME)
384         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
385         # and there is no TransformMarcFieldToKoha
386         my $temp_item_marc = MARC::Record->new();
387         $temp_item_marc->append_fields($item_field);
388     
389         # add biblionumber and biblioitemnumber
390         my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' );
391         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
392         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
393         $item->{'biblionumber'} = $biblionumber;
394         $item->{'biblioitemnumber'} = $biblioitemnumber;
395
396         # check for duplicate barcode
397         my %item_errors = CheckItemPreSave($item);
398         if (%item_errors) {
399             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
400             push @bad_item_fields, $item_field;
401             next ITEMFIELD;
402         }
403
404         _set_defaults_for_add($item);
405         _set_derived_columns_for_add($item);
406         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
407         warn $error if $error;
408         push @itemnumbers, $itemnumber; # FIXME not checking error
409         $item->{'itemnumber'} = $itemnumber;
410
411         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
412
413         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
414         $item_field->replace_with($new_item_marc->field($itemtag));
415     }
416
417     # remove any MARC item fields for rejected items
418     foreach my $item_field (@bad_item_fields) {
419         $record->delete_field($item_field);
420     }
421
422     # update the MARC biblio
423  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
424
425     return (\@itemnumbers, \@errors);
426 }
427
428 =head2 ModItemFromMarc
429
430   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
431
432 This function updates an item record based on a supplied
433 C<MARC::Record> object containing an embedded item field.
434 This API is meant for the use of C<additem.pl>; for 
435 other purposes, C<ModItem> should be used.
436
437 This function uses the hash %default_values_for_mod_from_marc,
438 which contains default values for item fields to
439 apply when modifying an item.  This is needed because
440 if an item field's value is cleared, TransformMarcToKoha
441 does not include the column in the
442 hash that's passed to ModItem, which without
443 use of this hash makes it impossible to clear
444 an item field's value.  See bug 2466.
445
446 Note that only columns that can be directly
447 changed from the cataloging and serials
448 item editors are included in this hash.
449
450 Returns item record
451
452 =cut
453
454 sub _build_default_values_for_mod_marc {
455     my ($frameworkcode) = @_;
456
457     my $cache     = Koha::Cache->get_instance();
458     my $cache_key = "default_value_for_mod_marc-$frameworkcode";
459     my $cached    = $cache->get_from_cache($cache_key);
460     return $cached if $cached;
461
462     my $default_values = {
463         barcode                  => undef,
464         booksellerid             => undef,
465         ccode                    => undef,
466         'items.cn_source'        => undef,
467         coded_location_qualifier => undef,
468         copynumber               => undef,
469         damaged                  => 0,
470         enumchron                => undef,
471         holdingbranch            => undef,
472         homebranch               => undef,
473         itemcallnumber           => undef,
474         itemlost                 => 0,
475         itemnotes                => undef,
476         itemnotes_nonpublic      => undef,
477         itype                    => undef,
478         location                 => undef,
479         permanent_location       => undef,
480         materials                => undef,
481         notforloan               => 0,
482         # paidfor => undef, # commented, see bug 12817
483         price                    => undef,
484         replacementprice         => undef,
485         replacementpricedate     => undef,
486         restricted               => undef,
487         stack                    => undef,
488         stocknumber              => undef,
489         uri                      => undef,
490         withdrawn                => 0,
491     };
492     my %default_values_for_mod_from_marc;
493     while ( my ( $field, $default_value ) = each %$default_values ) {
494         my $kohafield = $field;
495         $kohafield =~ s|^([^\.]+)$|items.$1|;
496         $default_values_for_mod_from_marc{$field} =
497           $default_value
498           if C4::Koha::IsKohaFieldLinked(
499             { kohafield => $kohafield, frameworkcode => $frameworkcode } );
500     }
501
502     $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
503     return \%default_values_for_mod_from_marc;
504 }
505
506 sub ModItemFromMarc {
507     my $item_marc = shift;
508     my $biblionumber = shift;
509     my $itemnumber = shift;
510
511     my $dbh           = C4::Context->dbh;
512     my $frameworkcode = GetFrameworkCode($biblionumber);
513     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
514
515     my $localitemmarc = MARC::Record->new;
516     $localitemmarc->append_fields( $item_marc->field($itemtag) );
517     my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode, 'items' );
518     my $default_values = _build_default_values_for_mod_marc($frameworkcode);
519     foreach my $item_field ( keys %$default_values ) {
520         $item->{$item_field} = $default_values->{$item_field}
521           unless exists $item->{$item_field};
522     }
523     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
524
525     ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
526     return $item;
527 }
528
529 =head2 ModItem
530
531   ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
532
533 Change one or more columns in an item record and update
534 the MARC representation of the item.
535
536 The first argument is a hashref mapping from item column
537 names to the new values.  The second and third arguments
538 are the biblionumber and itemnumber, respectively.
539
540 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
541 an arrayref containing subfields present in the original MARC
542 representation of the item (e.g., from the item editor) that are
543 not mapped to C<items> columns directly but should instead
544 be stored in C<items.more_subfields_xml> and included in 
545 the biblio items tag for display and indexing.
546
547 If one of the changed columns is used to calculate
548 the derived value of a column such as C<items.cn_sort>, 
549 this routine will perform the necessary calculation
550 and set the value.
551
552 =cut
553
554 sub ModItem {
555     my $item = shift;
556     my $biblionumber = shift;
557     my $itemnumber = shift;
558
559     # if $biblionumber is undefined, get it from the current item
560     unless (defined $biblionumber) {
561         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
562     }
563
564     my $dbh           = @_ ? shift : C4::Context->dbh;
565     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
566     
567     my $unlinked_item_subfields;  
568     if (@_) {
569         $unlinked_item_subfields = shift;
570         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
571     };
572
573     $item->{'itemnumber'} = $itemnumber or return;
574
575     my @fields = qw( itemlost withdrawn );
576
577     # Only call GetItem if we need to set an "on" date field
578     if ( $item->{itemlost} || $item->{withdrawn} ) {
579         my $pre_mod_item = GetItem( $item->{'itemnumber'} );
580         for my $field (@fields) {
581             if (    defined( $item->{$field} )
582                 and not $pre_mod_item->{$field}
583                 and $item->{$field} )
584             {
585                 $item->{ $field . '_on' } =
586                   DateTime::Format::MySQL->format_datetime( dt_from_string() );
587             }
588         }
589     }
590
591     # If the field is defined but empty, we are removing and,
592     # and thus need to clear out the 'on' field as well
593     for my $field (@fields) {
594         if ( defined( $item->{$field} ) && !$item->{$field} ) {
595             $item->{ $field . '_on' } = undef;
596         }
597     }
598
599
600     _set_derived_columns_for_mod($item);
601     _do_column_fixes_for_mod($item);
602     # FIXME add checks
603     # duplicate barcode
604     # attempt to change itemnumber
605     # attempt to change biblionumber (if we want
606     # an API to relink an item to a different bib,
607     # it should be a separate function)
608
609     # update items table
610     _koha_modify_item($item);
611
612     # request that bib be reindexed so that searching on current
613     # item status is possible
614     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
615
616     logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
617 }
618
619 =head2 ModItemTransfer
620
621   ModItemTransfer($itenumber, $frombranch, $tobranch);
622
623 Marks an item as being transferred from one branch
624 to another.
625
626 =cut
627
628 sub ModItemTransfer {
629     my ( $itemnumber, $frombranch, $tobranch ) = @_;
630
631     my $dbh = C4::Context->dbh;
632
633     # Remove the 'shelving cart' location status if it is being used.
634     CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
635
636     #new entry in branchtransfers....
637     my $sth = $dbh->prepare(
638         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
639         VALUES (?, ?, NOW(), ?)");
640     $sth->execute($itemnumber, $frombranch, $tobranch);
641
642     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
643     ModDateLastSeen($itemnumber);
644     return;
645 }
646
647 =head2 ModDateLastSeen
648
649   ModDateLastSeen($itemnum);
650
651 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
652 C<$itemnum> is the item number
653
654 =cut
655
656 sub ModDateLastSeen {
657     my ($itemnumber) = @_;
658     
659     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
660     ModItem({ itemlost => 0, datelastseen => $today }, undef, $itemnumber);
661 }
662
663 =head2 DelItem
664
665   DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
666
667 Exported function (core API) for deleting an item record in Koha.
668
669 =cut
670
671 sub DelItem {
672     my ( $params ) = @_;
673
674     my $itemnumber   = $params->{itemnumber};
675     my $biblionumber = $params->{biblionumber};
676
677     unless ($biblionumber) {
678         $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
679     }
680
681     # If there is no biblionumber for the given itemnumber, there is nothing to delete
682     return 0 unless $biblionumber;
683
684     # FIXME check the item has no current issues
685     my $deleted = _koha_delete_item( $itemnumber );
686
687     # get the MARC record
688     my $record = GetMarcBiblio($biblionumber);
689     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
690
691     #search item field code
692     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
693     return $deleted;
694 }
695
696 =head2 CheckItemPreSave
697
698     my $item_ref = TransformMarcToKoha($marc, 'items');
699     # do stuff
700     my %errors = CheckItemPreSave($item_ref);
701     if (exists $errors{'duplicate_barcode'}) {
702         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
703     } elsif (exists $errors{'invalid_homebranch'}) {
704         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
705     } elsif (exists $errors{'invalid_holdingbranch'}) {
706         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
707     } else {
708         print "item is OK";
709     }
710
711 Given a hashref containing item fields, determine if it can be
712 inserted or updated in the database.  Specifically, checks for
713 database integrity issues, and returns a hash containing any
714 of the following keys, if applicable.
715
716 =over 2
717
718 =item duplicate_barcode
719
720 Barcode, if it duplicates one already found in the database.
721
722 =item invalid_homebranch
723
724 Home branch, if not defined in branches table.
725
726 =item invalid_holdingbranch
727
728 Holding branch, if not defined in branches table.
729
730 =back
731
732 This function does NOT implement any policy-related checks,
733 e.g., whether current operator is allowed to save an
734 item that has a given branch code.
735
736 =cut
737
738 sub CheckItemPreSave {
739     my $item_ref = shift;
740     require C4::Branch;
741
742     my %errors = ();
743
744     # check for duplicate barcode
745     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
746         my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
747         if ($existing_itemnumber) {
748             if (!exists $item_ref->{'itemnumber'}                       # new item
749                 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
750                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
751             }
752         }
753     }
754
755     # check for valid home branch
756     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
757         my $branch_name = C4::Branch::GetBranchName($item_ref->{'homebranch'});
758         unless (defined $branch_name) {
759             # relies on fact that branches.branchname is a non-NULL column,
760             # so GetBranchName returns undef only if branch does not exist
761             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
762         }
763     }
764
765     # check for valid holding branch
766     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
767         my $branch_name = C4::Branch::GetBranchName($item_ref->{'holdingbranch'});
768         unless (defined $branch_name) {
769             # relies on fact that branches.branchname is a non-NULL column,
770             # so GetBranchName returns undef only if branch does not exist
771             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
772         }
773     }
774
775     return %errors;
776
777 }
778
779 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
780
781 The following functions provide various ways of 
782 getting an item record, a set of item records, or
783 lists of authorized values for certain item fields.
784
785 Some of the functions in this group are candidates
786 for refactoring -- for example, some of the code
787 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
788 has copy-and-paste work.
789
790 =cut
791
792 =head2 GetItemStatus
793
794   $itemstatushash = GetItemStatus($fwkcode);
795
796 Returns a list of valid values for the
797 C<items.notforloan> field.
798
799 NOTE: does B<not> return an individual item's
800 status.
801
802 Can be MARC dependent.
803 fwkcode is optional.
804 But basically could be can be loan or not
805 Create a status selector with the following code
806
807 =head3 in PERL SCRIPT
808
809  my $itemstatushash = getitemstatus;
810  my @itemstatusloop;
811  foreach my $thisstatus (keys %$itemstatushash) {
812      my %row =(value => $thisstatus,
813                  statusname => $itemstatushash->{$thisstatus}->{'statusname'},
814              );
815      push @itemstatusloop, \%row;
816  }
817  $template->param(statusloop=>\@itemstatusloop);
818
819 =head3 in TEMPLATE
820
821 <select name="statusloop" id="statusloop">
822     <option value="">Default</option>
823     [% FOREACH statusloo IN statusloop %]
824         [% IF ( statusloo.selected ) %]
825             <option value="[% statusloo.value %]" selected="selected">[% statusloo.statusname %]</option>
826         [% ELSE %]
827             <option value="[% statusloo.value %]">[% statusloo.statusname %]</option>
828         [% END %]
829     [% END %]
830 </select>
831
832 =cut
833
834 sub GetItemStatus {
835
836     # returns a reference to a hash of references to status...
837     my ($fwk) = @_;
838     my %itemstatus;
839     my $dbh = C4::Context->dbh;
840     my $sth;
841     $fwk = '' unless ($fwk);
842     my ( $tag, $subfield ) =
843       GetMarcFromKohaField( "items.notforloan", $fwk );
844     if ( $tag and $subfield ) {
845         my $sth =
846           $dbh->prepare(
847             "SELECT authorised_value
848             FROM marc_subfield_structure
849             WHERE tagfield=?
850                 AND tagsubfield=?
851                 AND frameworkcode=?
852             "
853           );
854         $sth->execute( $tag, $subfield, $fwk );
855         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
856             my $authvalsth =
857               $dbh->prepare(
858                 "SELECT authorised_value,lib
859                 FROM authorised_values 
860                 WHERE category=? 
861                 ORDER BY lib
862                 "
863               );
864             $authvalsth->execute($authorisedvaluecat);
865             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
866                 $itemstatus{$authorisedvalue} = $lib;
867             }
868             return \%itemstatus;
869         }
870         else {
871
872             #No authvalue list
873             # build default
874         }
875     }
876
877     #No authvalue list
878     #build default
879     $itemstatus{"1"} = "Not For Loan";
880     return \%itemstatus;
881 }
882
883 =head2 GetItemLocation
884
885   $itemlochash = GetItemLocation($fwk);
886
887 Returns a list of valid values for the
888 C<items.location> field.
889
890 NOTE: does B<not> return an individual item's
891 location.
892
893 where fwk stands for an optional framework code.
894 Create a location selector with the following code
895
896 =head3 in PERL SCRIPT
897
898   my $itemlochash = getitemlocation;
899   my @itemlocloop;
900   foreach my $thisloc (keys %$itemlochash) {
901       my $selected = 1 if $thisbranch eq $branch;
902       my %row =(locval => $thisloc,
903                   selected => $selected,
904                   locname => $itemlochash->{$thisloc},
905                );
906       push @itemlocloop, \%row;
907   }
908   $template->param(itemlocationloop => \@itemlocloop);
909
910 =head3 in TEMPLATE
911
912   <select name="location">
913       <option value="">Default</option>
914   <!-- TMPL_LOOP name="itemlocationloop" -->
915       <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
916   <!-- /TMPL_LOOP -->
917   </select>
918
919 =cut
920
921 sub GetItemLocation {
922
923     # returns a reference to a hash of references to location...
924     my ($fwk) = @_;
925     my %itemlocation;
926     my $dbh = C4::Context->dbh;
927     my $sth;
928     $fwk = '' unless ($fwk);
929     my ( $tag, $subfield ) =
930       GetMarcFromKohaField( "items.location", $fwk );
931     if ( $tag and $subfield ) {
932         my $sth =
933           $dbh->prepare(
934             "SELECT authorised_value
935             FROM marc_subfield_structure 
936             WHERE tagfield=? 
937                 AND tagsubfield=? 
938                 AND frameworkcode=?"
939           );
940         $sth->execute( $tag, $subfield, $fwk );
941         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
942             my $authvalsth =
943               $dbh->prepare(
944                 "SELECT authorised_value,lib
945                 FROM authorised_values
946                 WHERE category=?
947                 ORDER BY lib"
948               );
949             $authvalsth->execute($authorisedvaluecat);
950             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
951                 $itemlocation{$authorisedvalue} = $lib;
952             }
953             return \%itemlocation;
954         }
955         else {
956
957             #No authvalue list
958             # build default
959         }
960     }
961
962     #No authvalue list
963     #build default
964     $itemlocation{"1"} = "Not For Loan";
965     return \%itemlocation;
966 }
967
968 =head2 GetLostItems
969
970   $items = GetLostItems( $where );
971
972 This function gets a list of lost items.
973
974 =over 2
975
976 =item input:
977
978 C<$where> is a hashref. it containts a field of the items table as key
979 and the value to match as value. For example:
980
981 { barcode    => 'abc123',
982   homebranch => 'CPL',    }
983
984 =item return:
985
986 C<$items> is a reference to an array full of hashrefs with columns
987 from the "items" table as keys.
988
989 =item usage in the perl script:
990
991   my $where = { barcode => '0001548' };
992   my $items = GetLostItems( $where );
993   $template->param( itemsloop => $items );
994
995 =back
996
997 =cut
998
999 sub GetLostItems {
1000     # Getting input args.
1001     my $where   = shift;
1002     my $dbh     = C4::Context->dbh;
1003
1004     my $query   = "
1005         SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
1006                itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
1007         FROM   items
1008             LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
1009             LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
1010             LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
1011         WHERE
1012                 authorised_values.category = 'LOST'
1013                 AND itemlost IS NOT NULL
1014                 AND itemlost <> 0
1015     ";
1016     my @query_parameters;
1017     foreach my $key (keys %$where) {
1018         $query .= " AND $key LIKE ?";
1019         push @query_parameters, "%$where->{$key}%";
1020     }
1021
1022     my $sth = $dbh->prepare($query);
1023     $sth->execute( @query_parameters );
1024     my $items = [];
1025     while ( my $row = $sth->fetchrow_hashref ){
1026         push @$items, $row;
1027     }
1028     return $items;
1029 }
1030
1031 =head2 GetItemsForInventory
1032
1033 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
1034   minlocation  => $minlocation,
1035   maxlocation  => $maxlocation,
1036   location     => $location,
1037   itemtype     => $itemtype,
1038   ignoreissued => $ignoreissued,
1039   datelastseen => $datelastseen,
1040   branchcode   => $branchcode,
1041   branch       => $branch,
1042   offset       => $offset,
1043   size         => $size,
1044   statushash   => $statushash,
1045   interface    => $interface,
1046 } );
1047
1048 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
1049
1050 The sub returns a reference to a list of hashes, each containing
1051 itemnumber, author, title, barcode, item callnumber, and date last
1052 seen. It is ordered by callnumber then title.
1053
1054 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
1055 the datelastseen can be used to specify that you want to see items not seen since a past date only.
1056 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
1057 $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.
1058
1059 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
1060
1061 =cut
1062
1063 sub GetItemsForInventory {
1064     my ( $parameters ) = @_;
1065     my $minlocation  = $parameters->{'minlocation'}  // '';
1066     my $maxlocation  = $parameters->{'maxlocation'}  // '';
1067     my $location     = $parameters->{'location'}     // '';
1068     my $itemtype     = $parameters->{'itemtype'}     // '';
1069     my $ignoreissued = $parameters->{'ignoreissued'} // '';
1070     my $datelastseen = $parameters->{'datelastseen'} // '';
1071     my $branchcode   = $parameters->{'branchcode'}   // '';
1072     my $branch       = $parameters->{'branch'}       // '';
1073     my $offset       = $parameters->{'offset'}       // '';
1074     my $size         = $parameters->{'size'}         // '';
1075     my $statushash   = $parameters->{'statushash'}   // '';
1076     my $interface    = $parameters->{'interface'}    // '';
1077
1078     my $dbh = C4::Context->dbh;
1079     my ( @bind_params, @where_strings );
1080
1081     my $select_columns = q{
1082         SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
1083     };
1084     my $select_count = q{SELECT COUNT(*)};
1085     my $query = q{
1086         FROM items
1087         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
1088         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
1089     };
1090     if ($statushash){
1091         for my $authvfield (keys %$statushash){
1092             if ( scalar @{$statushash->{$authvfield}} > 0 ){
1093                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
1094                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
1095             }
1096         }
1097     }
1098
1099     if ($minlocation) {
1100         push @where_strings, 'itemcallnumber >= ?';
1101         push @bind_params, $minlocation;
1102     }
1103
1104     if ($maxlocation) {
1105         push @where_strings, 'itemcallnumber <= ?';
1106         push @bind_params, $maxlocation;
1107     }
1108
1109     if ($datelastseen) {
1110         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
1111         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
1112         push @bind_params, $datelastseen;
1113     }
1114
1115     if ( $location ) {
1116         push @where_strings, 'items.location = ?';
1117         push @bind_params, $location;
1118     }
1119
1120     if ( $branchcode ) {
1121         if($branch eq "homebranch"){
1122         push @where_strings, 'items.homebranch = ?';
1123         }else{
1124             push @where_strings, 'items.holdingbranch = ?';
1125         }
1126         push @bind_params, $branchcode;
1127     }
1128
1129     if ( $itemtype ) {
1130         push @where_strings, 'biblioitems.itemtype = ?';
1131         push @bind_params, $itemtype;
1132     }
1133
1134     if ( $ignoreissued) {
1135         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1136         push @where_strings, 'issues.date_due IS NULL';
1137     }
1138
1139     if ( @where_strings ) {
1140         $query .= 'WHERE ';
1141         $query .= join ' AND ', @where_strings;
1142     }
1143     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1144     my $count_query = $select_count . $query;
1145     $query .= " LIMIT $offset, $size" if ($offset and $size);
1146     $query = $select_columns . $query;
1147     my $sth = $dbh->prepare($query);
1148     $sth->execute( @bind_params );
1149
1150     my @results = ();
1151     my $tmpresults = $sth->fetchall_arrayref({});
1152     $sth = $dbh->prepare( $count_query );
1153     $sth->execute( @bind_params );
1154     my ($iTotalRecords) = $sth->fetchrow_array();
1155
1156     my $avmapping = C4::Koha::GetKohaAuthorisedValuesMapping( {
1157                       interface => $interface
1158                     } );
1159     foreach my $row (@$tmpresults) {
1160
1161         # Auth values
1162         foreach (keys %$row) {
1163             if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
1164                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
1165             }
1166         }
1167         push @results, $row;
1168     }
1169
1170     return (\@results, $iTotalRecords);
1171 }
1172
1173 =head2 GetItemsCount
1174
1175   $count = &GetItemsCount( $biblionumber);
1176
1177 This function return count of item with $biblionumber
1178
1179 =cut
1180
1181 sub GetItemsCount {
1182     my ( $biblionumber ) = @_;
1183     my $dbh = C4::Context->dbh;
1184     my $query = "SELECT count(*)
1185           FROM  items 
1186           WHERE biblionumber=?";
1187     my $sth = $dbh->prepare($query);
1188     $sth->execute($biblionumber);
1189     my $count = $sth->fetchrow;  
1190     return ($count);
1191 }
1192
1193 =head2 GetItemInfosOf
1194
1195   GetItemInfosOf(@itemnumbers);
1196
1197 =cut
1198
1199 sub GetItemInfosOf {
1200     my @itemnumbers = @_;
1201
1202     my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
1203
1204     my $query = "
1205         SELECT *
1206         FROM items
1207         WHERE itemnumber IN ($itemnumber_values)
1208     ";
1209     return get_infos_of( $query, 'itemnumber' );
1210 }
1211
1212 =head2 GetItemsByBiblioitemnumber
1213
1214   GetItemsByBiblioitemnumber($biblioitemnumber);
1215
1216 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1217 Called by C<C4::XISBN>
1218
1219 =cut
1220
1221 sub GetItemsByBiblioitemnumber {
1222     my ( $bibitem ) = @_;
1223     my $dbh = C4::Context->dbh;
1224     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1225     # Get all items attached to a biblioitem
1226     my $i = 0;
1227     my @results; 
1228     $sth->execute($bibitem) || die $sth->errstr;
1229     while ( my $data = $sth->fetchrow_hashref ) {  
1230         # Foreach item, get circulation information
1231         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1232                                    WHERE itemnumber = ?
1233                                    AND issues.borrowernumber = borrowers.borrowernumber"
1234         );
1235         $sth2->execute( $data->{'itemnumber'} );
1236         if ( my $data2 = $sth2->fetchrow_hashref ) {
1237             # if item is out, set the due date and who it is out too
1238             $data->{'date_due'}   = $data2->{'date_due'};
1239             $data->{'cardnumber'} = $data2->{'cardnumber'};
1240             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1241         }
1242         else {
1243             # set date_due to blank, so in the template we check itemlost, and withdrawn
1244             $data->{'date_due'} = '';                                                                                                         
1245         }    # else         
1246         # Find the last 3 people who borrowed this item.                  
1247         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1248                       AND old_issues.borrowernumber = borrowers.borrowernumber
1249                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1250         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1251         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1252         my $i2 = 0;
1253         while ( my $data2 = $sth2->fetchrow_hashref ) {
1254             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1255             $data->{"card$i2"}      = $data2->{'cardnumber'};
1256             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1257             $i2++;
1258         }
1259         push(@results,$data);
1260     } 
1261     return (\@results); 
1262 }
1263
1264 =head2 GetItemsInfo
1265
1266   @results = GetItemsInfo($biblionumber);
1267
1268 Returns information about items with the given biblionumber.
1269
1270 C<GetItemsInfo> returns a list of references-to-hash. Each element
1271 contains a number of keys. Most of them are attributes from the
1272 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1273 Koha database. Other keys include:
1274
1275 =over 2
1276
1277 =item C<$data-E<gt>{branchname}>
1278
1279 The name (not the code) of the branch to which the book belongs.
1280
1281 =item C<$data-E<gt>{datelastseen}>
1282
1283 This is simply C<items.datelastseen>, except that while the date is
1284 stored in YYYY-MM-DD format in the database, here it is converted to
1285 DD/MM/YYYY format. A NULL date is returned as C<//>.
1286
1287 =item C<$data-E<gt>{datedue}>
1288
1289 =item C<$data-E<gt>{class}>
1290
1291 This is the concatenation of C<biblioitems.classification>, the book's
1292 Dewey code, and C<biblioitems.subclass>.
1293
1294 =item C<$data-E<gt>{ocount}>
1295
1296 I think this is the number of copies of the book available.
1297
1298 =item C<$data-E<gt>{order}>
1299
1300 If this is set, it is set to C<One Order>.
1301
1302 =back
1303
1304 =cut
1305
1306 sub GetItemsInfo {
1307     my ( $biblionumber ) = @_;
1308     my $dbh   = C4::Context->dbh;
1309     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1310     require C4::Languages;
1311     my $language = C4::Languages::getlanguage();
1312     my $query = "
1313     SELECT items.*,
1314            biblio.*,
1315            biblioitems.volume,
1316            biblioitems.number,
1317            biblioitems.itemtype,
1318            biblioitems.isbn,
1319            biblioitems.issn,
1320            biblioitems.publicationyear,
1321            biblioitems.publishercode,
1322            biblioitems.volumedate,
1323            biblioitems.volumedesc,
1324            biblioitems.lccn,
1325            biblioitems.url,
1326            items.notforloan as itemnotforloan,
1327            issues.borrowernumber,
1328            issues.date_due as datedue,
1329            issues.onsite_checkout,
1330            borrowers.cardnumber,
1331            borrowers.surname,
1332            borrowers.firstname,
1333            borrowers.branchcode as bcode,
1334            serial.serialseq,
1335            serial.publisheddate,
1336            itemtypes.description,
1337            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1338            itemtypes.notforloan as notforloan_per_itemtype,
1339            holding.branchurl,
1340            holding.branchname,
1341            holding.opac_info as holding_branch_opac_info,
1342            home.opac_info as home_branch_opac_info
1343     ";
1344     $query .= "
1345      FROM items
1346      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1347      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1348      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
1349      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1350      LEFT JOIN issues USING (itemnumber)
1351      LEFT JOIN borrowers USING (borrowernumber)
1352      LEFT JOIN serialitems USING (itemnumber)
1353      LEFT JOIN serial USING (serialid)
1354      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
1355      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1356     $query .= q|
1357     LEFT JOIN localization ON itemtypes.itemtype = localization.code
1358         AND localization.entity = 'itemtypes'
1359         AND localization.lang = ?
1360     |;
1361
1362     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1363     my $sth = $dbh->prepare($query);
1364     $sth->execute($language, $biblionumber);
1365     my $i = 0;
1366     my @results;
1367     my $serial;
1368
1369     my $userenv = C4::Context->userenv;
1370     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
1371     while ( my $data = $sth->fetchrow_hashref ) {
1372         if ( $data->{borrowernumber} && $want_not_same_branch) {
1373             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
1374         }
1375
1376         $serial ||= $data->{'serial'};
1377
1378         # get notforloan complete status if applicable
1379         if ( my $code = C4::Koha::GetAuthValCode( 'items.notforloan', $data->{frameworkcode} ) ) {
1380             $data->{notforloanvalue}     = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan} );
1381             $data->{notforloanvalueopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan}, 1 );
1382         }
1383
1384         # get restricted status and description if applicable
1385         if ( my $code = C4::Koha::GetAuthValCode( 'items.restricted', $data->{frameworkcode} ) ) {
1386             $data->{restrictedopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted}, 1 );
1387             $data->{restricted}     = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted} );
1388         }
1389
1390         # my stack procedures
1391         if ( my $code = C4::Koha::GetAuthValCode( 'items.stack', $data->{frameworkcode} ) ) {
1392             $data->{stack}          = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{stack} );
1393         }
1394
1395         # Find the last 3 people who borrowed this item.
1396         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1397                                     WHERE itemnumber = ?
1398                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1399                                     ORDER BY returndate DESC
1400                                     LIMIT 3");
1401         $sth2->execute($data->{'itemnumber'});
1402         my $ii = 0;
1403         while (my $data2 = $sth2->fetchrow_hashref()) {
1404             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1405             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1406             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1407             $ii++;
1408         }
1409
1410         $results[$i] = $data;
1411         $i++;
1412     }
1413
1414     return $serial
1415         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1416         : @results;
1417 }
1418
1419 =head2 GetItemsLocationInfo
1420
1421   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1422
1423 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1424
1425 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1426
1427 =over 2
1428
1429 =item C<$data-E<gt>{homebranch}>
1430
1431 Branch Name of the item's homebranch
1432
1433 =item C<$data-E<gt>{holdingbranch}>
1434
1435 Branch Name of the item's holdingbranch
1436
1437 =item C<$data-E<gt>{location}>
1438
1439 Item's shelving location code
1440
1441 =item C<$data-E<gt>{location_intranet}>
1442
1443 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1444
1445 =item C<$data-E<gt>{location_opac}>
1446
1447 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
1448 description is set.
1449
1450 =item C<$data-E<gt>{itemcallnumber}>
1451
1452 Item's itemcallnumber
1453
1454 =item C<$data-E<gt>{cn_sort}>
1455
1456 Item's call number normalized for sorting
1457
1458 =back
1459   
1460 =cut
1461
1462 sub GetItemsLocationInfo {
1463         my $biblionumber = shift;
1464         my @results;
1465
1466         my $dbh = C4::Context->dbh;
1467         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
1468                             location, itemcallnumber, cn_sort
1469                      FROM items, branches as a, branches as b
1470                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
1471                      AND biblionumber = ?
1472                      ORDER BY cn_sort ASC";
1473         my $sth = $dbh->prepare($query);
1474         $sth->execute($biblionumber);
1475
1476         while ( my $data = $sth->fetchrow_hashref ) {
1477              $data->{location_intranet} = GetKohaAuthorisedValueLib('LOC', $data->{location});
1478              $data->{location_opac}= GetKohaAuthorisedValueLib('LOC', $data->{location}, 1);
1479              push @results, $data;
1480         }
1481         return @results;
1482 }
1483
1484 =head2 GetHostItemsInfo
1485
1486         $hostiteminfo = GetHostItemsInfo($hostfield);
1487         Returns the iteminfo for items linked to records via a host field
1488
1489 =cut
1490
1491 sub GetHostItemsInfo {
1492         my ($record) = @_;
1493         my @returnitemsInfo;
1494
1495         if (C4::Context->preference('marcflavour') eq 'MARC21' ||
1496         C4::Context->preference('marcflavour') eq 'NORMARC'){
1497             foreach my $hostfield ( $record->field('773') ) {
1498                 my $hostbiblionumber = $hostfield->subfield("0");
1499                 my $linkeditemnumber = $hostfield->subfield("9");
1500                 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1501                 foreach my $hostitemInfo (@hostitemInfos){
1502                         if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1503                                 push (@returnitemsInfo,$hostitemInfo);
1504                                 last;
1505                         }
1506                 }
1507             }
1508         } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
1509             foreach my $hostfield ( $record->field('461') ) {
1510                 my $hostbiblionumber = $hostfield->subfield("0");
1511                 my $linkeditemnumber = $hostfield->subfield("9");
1512                 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1513                 foreach my $hostitemInfo (@hostitemInfos){
1514                         if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1515                                 push (@returnitemsInfo,$hostitemInfo);
1516                                 last;
1517                         }
1518                 }
1519             }
1520         }
1521         return @returnitemsInfo;
1522 }
1523
1524
1525 =head2 GetLastAcquisitions
1526
1527   my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 
1528                                     'itemtypes' => ('BK','BD')}, 10);
1529
1530 =cut
1531
1532 sub  GetLastAcquisitions {
1533         my ($data,$max) = @_;
1534
1535         my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1536         
1537         my $number_of_branches = @{$data->{branches}};
1538         my $number_of_itemtypes   = @{$data->{itemtypes}};
1539         
1540         
1541         my @where = ('WHERE 1 '); 
1542         $number_of_branches and push @where
1543            , 'AND holdingbranch IN (' 
1544            , join(',', ('?') x $number_of_branches )
1545            , ')'
1546          ;
1547         
1548         $number_of_itemtypes and push @where
1549            , "AND $itemtype IN (" 
1550            , join(',', ('?') x $number_of_itemtypes )
1551            , ')'
1552          ;
1553
1554         my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1555                                  FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
1556                                     RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1557                                     @where
1558                                     GROUP BY biblio.biblionumber 
1559                                     ORDER BY dateaccessioned DESC LIMIT $max";
1560
1561         my $dbh = C4::Context->dbh;
1562         my $sth = $dbh->prepare($query);
1563     
1564     $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1565         
1566         my @results;
1567         while( my $row = $sth->fetchrow_hashref){
1568                 push @results, {date => $row->{dateaccessioned} 
1569                                                 , biblionumber => $row->{biblionumber}
1570                                                 , title => $row->{title}};
1571         }
1572         
1573         return @results;
1574 }
1575
1576 =head2 GetItemnumbersForBiblio
1577
1578   my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1579
1580 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1581
1582 =cut
1583
1584 sub GetItemnumbersForBiblio {
1585     my $biblionumber = shift;
1586     my @items;
1587     my $dbh = C4::Context->dbh;
1588     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1589     $sth->execute($biblionumber);
1590     while (my $result = $sth->fetchrow_hashref) {
1591         push @items, $result->{'itemnumber'};
1592     }
1593     return \@items;
1594 }
1595
1596 =head2 get_itemnumbers_of
1597
1598   my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1599
1600 Given a list of biblionumbers, return the list of corresponding itemnumbers
1601 for each biblionumber.
1602
1603 Return a reference on a hash where keys are biblionumbers and values are
1604 references on array of itemnumbers.
1605
1606 =cut
1607
1608 sub get_itemnumbers_of {
1609     my @biblionumbers = @_;
1610
1611     my $dbh = C4::Context->dbh;
1612
1613     my $query = '
1614         SELECT itemnumber,
1615             biblionumber
1616         FROM items
1617         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1618     ';
1619     my $sth = $dbh->prepare($query);
1620     $sth->execute(@biblionumbers);
1621
1622     my %itemnumbers_of;
1623
1624     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1625         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1626     }
1627
1628     return \%itemnumbers_of;
1629 }
1630
1631 =head2 get_hostitemnumbers_of
1632
1633   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1634
1635 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1636
1637 Return a reference on a hash where key is a biblionumber and values are
1638 references on array of itemnumbers.
1639
1640 =cut
1641
1642
1643 sub get_hostitemnumbers_of {
1644         my ($biblionumber) = @_;
1645         my $marcrecord = GetMarcBiblio($biblionumber);
1646         my (@returnhostitemnumbers,$tag, $biblio_s, $item_s);
1647         
1648         my $marcflavor = C4::Context->preference('marcflavour');
1649         if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
1650         $tag='773';
1651         $biblio_s='0';
1652         $item_s='9';
1653     } elsif ($marcflavor eq 'UNIMARC') {
1654         $tag='461';
1655         $biblio_s='0';
1656         $item_s='9';
1657     }
1658
1659     foreach my $hostfield ( $marcrecord->field($tag) ) {
1660         my $hostbiblionumber = $hostfield->subfield($biblio_s);
1661         my $linkeditemnumber = $hostfield->subfield($item_s);
1662         my @itemnumbers;
1663         if (my $itemnumbers = get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber})
1664         {
1665             @itemnumbers = @$itemnumbers;
1666         }
1667         foreach my $itemnumber (@itemnumbers){
1668             if ($itemnumber eq $linkeditemnumber){
1669                 push (@returnhostitemnumbers,$itemnumber);
1670                 last;
1671             }
1672         }
1673     }
1674     return @returnhostitemnumbers;
1675 }
1676
1677
1678 =head2 GetItemnumberFromBarcode
1679
1680   $result = GetItemnumberFromBarcode($barcode);
1681
1682 =cut
1683
1684 sub GetItemnumberFromBarcode {
1685     my ($barcode) = @_;
1686     my $dbh = C4::Context->dbh;
1687
1688     my $rq =
1689       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1690     $rq->execute($barcode);
1691     my ($result) = $rq->fetchrow;
1692     return ($result);
1693 }
1694
1695 =head2 GetBarcodeFromItemnumber
1696
1697   $result = GetBarcodeFromItemnumber($itemnumber);
1698
1699 =cut
1700
1701 sub GetBarcodeFromItemnumber {
1702     my ($itemnumber) = @_;
1703     my $dbh = C4::Context->dbh;
1704
1705     my $rq =
1706       $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1707     $rq->execute($itemnumber);
1708     my ($result) = $rq->fetchrow;
1709     return ($result);
1710 }
1711
1712 =head2 GetHiddenItemnumbers
1713
1714     my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1715
1716 Given a list of items it checks which should be hidden from the OPAC given
1717 the current configuration. Returns a list of itemnumbers corresponding to
1718 those that should be hidden.
1719
1720 =cut
1721
1722 sub GetHiddenItemnumbers {
1723     my (@items) = @_;
1724     my @resultitems;
1725
1726     my $yaml = C4::Context->preference('OpacHiddenItems');
1727     return () if (! $yaml =~ /\S/ );
1728     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1729     my $hidingrules;
1730     eval {
1731         $hidingrules = YAML::Load($yaml);
1732     };
1733     if ($@) {
1734         warn "Unable to parse OpacHiddenItems syspref : $@";
1735         return ();
1736     }
1737     my $dbh = C4::Context->dbh;
1738
1739     # For each item
1740     foreach my $item (@items) {
1741
1742         # We check each rule
1743         foreach my $field (keys %$hidingrules) {
1744             my $val;
1745             if (exists $item->{$field}) {
1746                 $val = $item->{$field};
1747             }
1748             else {
1749                 my $query = "SELECT $field from items where itemnumber = ?";
1750                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1751             }
1752             $val = '' unless defined $val;
1753
1754             # If the results matches the values in the yaml file
1755             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1756
1757                 # We add the itemnumber to the list
1758                 push @resultitems, $item->{'itemnumber'};
1759
1760                 # If at least one rule matched for an item, no need to test the others
1761                 last;
1762             }
1763         }
1764     }
1765     return @resultitems;
1766 }
1767
1768 =head3 get_item_authorised_values
1769
1770 find the types and values for all authorised values assigned to this item.
1771
1772 parameters: itemnumber
1773
1774 returns: a hashref malling the authorised value to the value set for this itemnumber
1775
1776     $authorised_values = {
1777              'CCODE'      => undef,
1778              'DAMAGED'    => '0',
1779              'LOC'        => '3',
1780              'LOST'       => '0'
1781              'NOT_LOAN'   => '0',
1782              'RESTRICTED' => undef,
1783              'STACK'      => undef,
1784              'WITHDRAWN'  => '0',
1785              'branches'   => 'CPL',
1786              'cn_source'  => undef,
1787              'itemtypes'  => 'SER',
1788            };
1789
1790 Notes: see C4::Biblio::get_biblio_authorised_values for a similar method at the biblio level.
1791
1792 =cut
1793
1794 sub get_item_authorised_values {
1795     my $itemnumber = shift;
1796
1797     # assume that these entries in the authorised_value table are item level.
1798     my $query = q(SELECT distinct authorised_value, kohafield
1799                     FROM marc_subfield_structure
1800                     WHERE kohafield like 'item%'
1801                       AND authorised_value != '' );
1802
1803     my $itemlevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
1804     my $iteminfo = GetItem( $itemnumber );
1805     # warn( Data::Dumper->Dump( [ $itemlevel_authorised_values ], [ 'itemlevel_authorised_values' ] ) );
1806     my $return;
1807     foreach my $this_authorised_value ( keys %$itemlevel_authorised_values ) {
1808         my $field = $itemlevel_authorised_values->{ $this_authorised_value }->{'kohafield'};
1809         $field =~ s/^items\.//;
1810         if ( exists $iteminfo->{ $field } ) {
1811             $return->{ $this_authorised_value } = $iteminfo->{ $field };
1812         }
1813     }
1814     # warn( Data::Dumper->Dump( [ $return ], [ 'return' ] ) );
1815     return $return;
1816 }
1817
1818 =head3 get_authorised_value_images
1819
1820 find a list of icons that are appropriate for display based on the
1821 authorised values for a biblio.
1822
1823 parameters: listref of authorised values, such as comes from
1824 get_item_authorised_values or
1825 from C4::Biblio::get_biblio_authorised_values
1826
1827 returns: listref of hashrefs for each image. Each hashref looks like this:
1828
1829       { imageurl => '/intranet-tmpl/prog/img/itemtypeimg/npl/WEB.gif',
1830         label    => '',
1831         category => '',
1832         value    => '', }
1833
1834 Notes: Currently, I put on the full path to the images on the staff
1835 side. This should either be configurable or not done at all. Since I
1836 have to deal with 'intranet' or 'opac' in
1837 get_biblio_authorised_values, perhaps I should be passing it in.
1838
1839 =cut
1840
1841 sub get_authorised_value_images {
1842     my $authorised_values = shift;
1843
1844     my @imagelist;
1845
1846     my $authorised_value_list = GetAuthorisedValues();
1847     # warn ( Data::Dumper->Dump( [ $authorised_value_list ], [ 'authorised_value_list' ] ) );
1848     foreach my $this_authorised_value ( @$authorised_value_list ) {
1849         if ( exists $authorised_values->{ $this_authorised_value->{'category'} }
1850              && $authorised_values->{ $this_authorised_value->{'category'} } eq $this_authorised_value->{'authorised_value'} ) {
1851             # warn ( Data::Dumper->Dump( [ $this_authorised_value ], [ 'this_authorised_value' ] ) );
1852             if ( defined $this_authorised_value->{'imageurl'} ) {
1853                 push @imagelist, { imageurl => C4::Koha::getitemtypeimagelocation( 'intranet', $this_authorised_value->{'imageurl'} ),
1854                                    label    => $this_authorised_value->{'lib'},
1855                                    category => $this_authorised_value->{'category'},
1856                                    value    => $this_authorised_value->{'authorised_value'}, };
1857             }
1858         }
1859     }
1860
1861     # warn ( Data::Dumper->Dump( [ \@imagelist ], [ 'imagelist' ] ) );
1862     return \@imagelist;
1863
1864 }
1865
1866 =head1 LIMITED USE FUNCTIONS
1867
1868 The following functions, while part of the public API,
1869 are not exported.  This is generally because they are
1870 meant to be used by only one script for a specific
1871 purpose, and should not be used in any other context
1872 without careful thought.
1873
1874 =cut
1875
1876 =head2 GetMarcItem
1877
1878   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1879
1880 Returns MARC::Record of the item passed in parameter.
1881 This function is meant for use only in C<cataloguing/additem.pl>,
1882 where it is needed to support that script's MARC-like
1883 editor.
1884
1885 =cut
1886
1887 sub GetMarcItem {
1888     my ( $biblionumber, $itemnumber ) = @_;
1889
1890     # GetMarcItem has been revised so that it does the following:
1891     #  1. Gets the item information from the items table.
1892     #  2. Converts it to a MARC field for storage in the bib record.
1893     #
1894     # The previous behavior was:
1895     #  1. Get the bib record.
1896     #  2. Return the MARC tag corresponding to the item record.
1897     #
1898     # The difference is that one treats the items row as authoritative,
1899     # while the other treats the MARC representation as authoritative
1900     # under certain circumstances.
1901
1902     my $itemrecord = GetItem($itemnumber);
1903
1904     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1905     # Also, don't emit a subfield if the underlying field is blank.
1906
1907     
1908     return Item2Marc($itemrecord,$biblionumber);
1909
1910 }
1911 sub Item2Marc {
1912         my ($itemrecord,$biblionumber)=@_;
1913     my $mungeditem = { 
1914         map {  
1915             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1916         } keys %{ $itemrecord } 
1917     };
1918     my $itemmarc = TransformKohaToMarc($mungeditem);
1919     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1920
1921     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1922     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1923                 foreach my $field ($itemmarc->field($itemtag)){
1924             $field->add_subfields(@$unlinked_item_subfields);
1925         }
1926     }
1927         return $itemmarc;
1928 }
1929
1930 =head1 PRIVATE FUNCTIONS AND VARIABLES
1931
1932 The following functions are not meant to be called
1933 directly, but are documented in order to explain
1934 the inner workings of C<C4::Items>.
1935
1936 =cut
1937
1938 =head2 %derived_columns
1939
1940 This hash keeps track of item columns that
1941 are strictly derived from other columns in
1942 the item record and are not meant to be set
1943 independently.
1944
1945 Each key in the hash should be the name of a
1946 column (as named by TransformMarcToKoha).  Each
1947 value should be hashref whose keys are the
1948 columns on which the derived column depends.  The
1949 hashref should also contain a 'BUILDER' key
1950 that is a reference to a sub that calculates
1951 the derived value.
1952
1953 =cut
1954
1955 my %derived_columns = (
1956     'items.cn_sort' => {
1957         'itemcallnumber' => 1,
1958         'items.cn_source' => 1,
1959         'BUILDER' => \&_calc_items_cn_sort,
1960     }
1961 );
1962
1963 =head2 _set_derived_columns_for_add 
1964
1965   _set_derived_column_for_add($item);
1966
1967 Given an item hash representing a new item to be added,
1968 calculate any derived columns.  Currently the only
1969 such column is C<items.cn_sort>.
1970
1971 =cut
1972
1973 sub _set_derived_columns_for_add {
1974     my $item = shift;
1975
1976     foreach my $column (keys %derived_columns) {
1977         my $builder = $derived_columns{$column}->{'BUILDER'};
1978         my $source_values = {};
1979         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1980             next if $source_column eq 'BUILDER';
1981             $source_values->{$source_column} = $item->{$source_column};
1982         }
1983         $builder->($item, $source_values);
1984     }
1985 }
1986
1987 =head2 _set_derived_columns_for_mod 
1988
1989   _set_derived_column_for_mod($item);
1990
1991 Given an item hash representing a new item to be modified.
1992 calculate any derived columns.  Currently the only
1993 such column is C<items.cn_sort>.
1994
1995 This routine differs from C<_set_derived_columns_for_add>
1996 in that it needs to handle partial item records.  In other
1997 words, the caller of C<ModItem> may have supplied only one
1998 or two columns to be changed, so this function needs to
1999 determine whether any of the columns to be changed affect
2000 any of the derived columns.  Also, if a derived column
2001 depends on more than one column, but the caller is not
2002 changing all of then, this routine retrieves the unchanged
2003 values from the database in order to ensure a correct
2004 calculation.
2005
2006 =cut
2007
2008 sub _set_derived_columns_for_mod {
2009     my $item = shift;
2010
2011     foreach my $column (keys %derived_columns) {
2012         my $builder = $derived_columns{$column}->{'BUILDER'};
2013         my $source_values = {};
2014         my %missing_sources = ();
2015         my $must_recalc = 0;
2016         foreach my $source_column (keys %{ $derived_columns{$column} }) {
2017             next if $source_column eq 'BUILDER';
2018             if (exists $item->{$source_column}) {
2019                 $must_recalc = 1;
2020                 $source_values->{$source_column} = $item->{$source_column};
2021             } else {
2022                 $missing_sources{$source_column} = 1;
2023             }
2024         }
2025         if ($must_recalc) {
2026             foreach my $source_column (keys %missing_sources) {
2027                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
2028             }
2029             $builder->($item, $source_values);
2030         }
2031     }
2032 }
2033
2034 =head2 _do_column_fixes_for_mod
2035
2036   _do_column_fixes_for_mod($item);
2037
2038 Given an item hashref containing one or more
2039 columns to modify, fix up certain values.
2040 Specifically, set to 0 any passed value
2041 of C<notforloan>, C<damaged>, C<itemlost>, or
2042 C<withdrawn> that is either undefined or
2043 contains the empty string.
2044
2045 =cut
2046
2047 sub _do_column_fixes_for_mod {
2048     my $item = shift;
2049
2050     if (exists $item->{'notforloan'} and
2051         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
2052         $item->{'notforloan'} = 0;
2053     }
2054     if (exists $item->{'damaged'} and
2055         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
2056         $item->{'damaged'} = 0;
2057     }
2058     if (exists $item->{'itemlost'} and
2059         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
2060         $item->{'itemlost'} = 0;
2061     }
2062     if (exists $item->{'withdrawn'} and
2063         (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
2064         $item->{'withdrawn'} = 0;
2065     }
2066     if (exists $item->{location}
2067         and $item->{location} ne 'CART'
2068         and $item->{location} ne 'PROC'
2069         and not $item->{permanent_location}
2070     ) {
2071         $item->{'permanent_location'} = $item->{'location'};
2072     }
2073     if (exists $item->{'timestamp'}) {
2074         delete $item->{'timestamp'};
2075     }
2076 }
2077
2078 =head2 _get_single_item_column
2079
2080   _get_single_item_column($column, $itemnumber);
2081
2082 Retrieves the value of a single column from an C<items>
2083 row specified by C<$itemnumber>.
2084
2085 =cut
2086
2087 sub _get_single_item_column {
2088     my $column = shift;
2089     my $itemnumber = shift;
2090     
2091     my $dbh = C4::Context->dbh;
2092     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
2093     $sth->execute($itemnumber);
2094     my ($value) = $sth->fetchrow();
2095     return $value; 
2096 }
2097
2098 =head2 _calc_items_cn_sort
2099
2100   _calc_items_cn_sort($item, $source_values);
2101
2102 Helper routine to calculate C<items.cn_sort>.
2103
2104 =cut
2105
2106 sub _calc_items_cn_sort {
2107     my $item = shift;
2108     my $source_values = shift;
2109
2110     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
2111 }
2112
2113 =head2 _set_defaults_for_add 
2114
2115   _set_defaults_for_add($item_hash);
2116
2117 Given an item hash representing an item to be added, set
2118 correct default values for columns whose default value
2119 is not handled by the DBMS.  This includes the following
2120 columns:
2121
2122 =over 2
2123
2124 =item * 
2125
2126 C<items.dateaccessioned>
2127
2128 =item *
2129
2130 C<items.notforloan>
2131
2132 =item *
2133
2134 C<items.damaged>
2135
2136 =item *
2137
2138 C<items.itemlost>
2139
2140 =item *
2141
2142 C<items.withdrawn>
2143
2144 =back
2145
2146 =cut
2147
2148 sub _set_defaults_for_add {
2149     my $item = shift;
2150     $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2151     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
2152 }
2153
2154 =head2 _koha_new_item
2155
2156   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
2157
2158 Perform the actual insert into the C<items> table.
2159
2160 =cut
2161
2162 sub _koha_new_item {
2163     my ( $item, $barcode ) = @_;
2164     my $dbh=C4::Context->dbh;  
2165     my $error;
2166     $item->{permanent_location} //= $item->{location};
2167     my $query =
2168            "INSERT INTO items SET
2169             biblionumber        = ?,
2170             biblioitemnumber    = ?,
2171             barcode             = ?,
2172             dateaccessioned     = ?,
2173             booksellerid        = ?,
2174             homebranch          = ?,
2175             price               = ?,
2176             replacementprice    = ?,
2177             replacementpricedate = ?,
2178             datelastborrowed    = ?,
2179             datelastseen        = ?,
2180             stack               = ?,
2181             notforloan          = ?,
2182             damaged             = ?,
2183             itemlost            = ?,
2184             withdrawn           = ?,
2185             itemcallnumber      = ?,
2186             coded_location_qualifier = ?,
2187             restricted          = ?,
2188             itemnotes           = ?,
2189             itemnotes_nonpublic = ?,
2190             holdingbranch       = ?,
2191             paidfor             = ?,
2192             location            = ?,
2193             permanent_location  = ?,
2194             onloan              = ?,
2195             issues              = ?,
2196             renewals            = ?,
2197             reserves            = ?,
2198             cn_source           = ?,
2199             cn_sort             = ?,
2200             ccode               = ?,
2201             itype               = ?,
2202             materials           = ?,
2203             uri                 = ?,
2204             enumchron           = ?,
2205             more_subfields_xml  = ?,
2206             copynumber          = ?,
2207             stocknumber         = ?
2208           ";
2209     my $sth = $dbh->prepare($query);
2210     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2211    $sth->execute(
2212             $item->{'biblionumber'},
2213             $item->{'biblioitemnumber'},
2214             $barcode,
2215             $item->{'dateaccessioned'},
2216             $item->{'booksellerid'},
2217             $item->{'homebranch'},
2218             $item->{'price'},
2219             $item->{'replacementprice'},
2220             $item->{'replacementpricedate'} || $today,
2221             $item->{datelastborrowed},
2222             $item->{datelastseen} || $today,
2223             $item->{stack},
2224             $item->{'notforloan'},
2225             $item->{'damaged'},
2226             $item->{'itemlost'},
2227             $item->{'withdrawn'},
2228             $item->{'itemcallnumber'},
2229             $item->{'coded_location_qualifier'},
2230             $item->{'restricted'},
2231             $item->{'itemnotes'},
2232             $item->{'itemnotes_nonpublic'},
2233             $item->{'holdingbranch'},
2234             $item->{'paidfor'},
2235             $item->{'location'},
2236             $item->{'permanent_location'},
2237             $item->{'onloan'},
2238             $item->{'issues'},
2239             $item->{'renewals'},
2240             $item->{'reserves'},
2241             $item->{'items.cn_source'},
2242             $item->{'items.cn_sort'},
2243             $item->{'ccode'},
2244             $item->{'itype'},
2245             $item->{'materials'},
2246             $item->{'uri'},
2247             $item->{'enumchron'},
2248             $item->{'more_subfields_xml'},
2249             $item->{'copynumber'},
2250             $item->{'stocknumber'},
2251     );
2252
2253     my $itemnumber;
2254     if ( defined $sth->errstr ) {
2255         $error.="ERROR in _koha_new_item $query".$sth->errstr;
2256     }
2257     else {
2258         $itemnumber = $dbh->{'mysql_insertid'};
2259     }
2260
2261     return ( $itemnumber, $error );
2262 }
2263
2264 =head2 MoveItemFromBiblio
2265
2266   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2267
2268 Moves an item from a biblio to another
2269
2270 Returns undef if the move failed or the biblionumber of the destination record otherwise
2271
2272 =cut
2273
2274 sub MoveItemFromBiblio {
2275     my ($itemnumber, $frombiblio, $tobiblio) = @_;
2276     my $dbh = C4::Context->dbh;
2277     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
2278         SELECT biblioitemnumber
2279         FROM biblioitems
2280         WHERE biblionumber = ?
2281     |, undef, $tobiblio );
2282     my $return = $dbh->do(q|
2283         UPDATE items
2284         SET biblioitemnumber = ?,
2285             biblionumber = ?
2286         WHERE itemnumber = ?
2287             AND biblionumber = ?
2288     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
2289     if ($return == 1) {
2290         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
2291         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
2292             # Checking if the item we want to move is in an order 
2293         require C4::Acquisition;
2294         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
2295             if ($order) {
2296                     # Replacing the biblionumber within the order if necessary
2297                     $order->{'biblionumber'} = $tobiblio;
2298                 C4::Acquisition::ModOrder($order);
2299             }
2300
2301         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
2302         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
2303             $dbh->do( qq|
2304                 UPDATE $table_name
2305                 SET biblionumber = ?
2306                 WHERE itemnumber = ?
2307             |, undef, $tobiblio, $itemnumber );
2308         }
2309         return $tobiblio;
2310         }
2311     return;
2312 }
2313
2314 =head2 DelItemCheck
2315
2316    DelItemCheck($dbh, $biblionumber, $itemnumber);
2317
2318 Exported function (core API) for deleting an item record in Koha if there no current issue.
2319
2320 =cut
2321
2322 sub DelItemCheck {
2323     my ( $dbh, $biblionumber, $itemnumber ) = @_;
2324     my $error;
2325
2326         my $countanalytics=GetAnalyticsCount($itemnumber);
2327
2328
2329     # check that there is no issue on this item before deletion.
2330     my $sth = $dbh->prepare(q{
2331         SELECT COUNT(*) FROM issues
2332         WHERE itemnumber = ?
2333     });
2334     $sth->execute($itemnumber);
2335     my ($onloan) = $sth->fetchrow;
2336
2337     my $item = GetItem($itemnumber);
2338
2339     if ($onloan){
2340         $error = "book_on_loan" 
2341     }
2342     elsif ( !C4::Context->IsSuperLibrarian()
2343         and C4::Context->preference("IndependentBranches")
2344         and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
2345     {
2346         $error = "not_same_branch";
2347     }
2348         else{
2349         # check it doesn't have a waiting reserve
2350         $sth = $dbh->prepare(q{
2351             SELECT COUNT(*) FROM reserves
2352             WHERE (found = 'W' OR found = 'T')
2353             AND itemnumber = ?
2354         });
2355         $sth->execute($itemnumber);
2356         my ($reserve) = $sth->fetchrow;
2357         if ($reserve){
2358             $error = "book_reserved";
2359         } elsif ($countanalytics > 0){
2360                 $error = "linked_analytics";
2361         } else {
2362             DelItem(
2363                 {
2364                     biblionumber => $biblionumber,
2365                     itemnumber   => $itemnumber
2366                 }
2367             );
2368             return 1;
2369         }
2370     }
2371     return $error;
2372 }
2373
2374 =head2 _koha_modify_item
2375
2376   my ($itemnumber,$error) =_koha_modify_item( $item );
2377
2378 Perform the actual update of the C<items> row.  Note that this
2379 routine accepts a hashref specifying the columns to update.
2380
2381 =cut
2382
2383 sub _koha_modify_item {
2384     my ( $item ) = @_;
2385     my $dbh=C4::Context->dbh;  
2386     my $error;
2387
2388     my $query = "UPDATE items SET ";
2389     my @bind;
2390     for my $key ( keys %$item ) {
2391         next if ( $key eq 'itemnumber' );
2392         $query.="$key=?,";
2393         push @bind, $item->{$key};
2394     }
2395     $query =~ s/,$//;
2396     $query .= " WHERE itemnumber=?";
2397     push @bind, $item->{'itemnumber'};
2398     my $sth = $dbh->prepare($query);
2399     $sth->execute(@bind);
2400     if ( $sth->err ) {
2401         $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2402         warn $error;
2403     }
2404     return ($item->{'itemnumber'},$error);
2405 }
2406
2407 =head2 _koha_delete_item
2408
2409   _koha_delete_item( $itemnum );
2410
2411 Internal function to delete an item record from the koha tables
2412
2413 =cut
2414
2415 sub _koha_delete_item {
2416     my ( $itemnum ) = @_;
2417
2418     my $dbh = C4::Context->dbh;
2419     # save the deleted item to deleteditems table
2420     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2421     $sth->execute($itemnum);
2422     my $data = $sth->fetchrow_hashref();
2423
2424     # There is no item to delete
2425     return 0 unless $data;
2426
2427     my $query = "INSERT INTO deleteditems SET ";
2428     my @bind  = ();
2429     foreach my $key ( keys %$data ) {
2430         next if ( $key eq 'timestamp' ); # timestamp will be set by db
2431         $query .= "$key = ?,";
2432         push( @bind, $data->{$key} );
2433     }
2434     $query =~ s/\,$//;
2435     $sth = $dbh->prepare($query);
2436     $sth->execute(@bind);
2437
2438     # delete from items table
2439     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2440     my $deleted = $sth->execute($itemnum);
2441     return ( $deleted == 1 ) ? 1 : 0;
2442 }
2443
2444 =head2 _marc_from_item_hash
2445
2446   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2447
2448 Given an item hash representing a complete item record,
2449 create a C<MARC::Record> object containing an embedded
2450 tag representing that item.
2451
2452 The third, optional parameter C<$unlinked_item_subfields> is
2453 an arrayref of subfields (not mapped to C<items> fields per the
2454 framework) to be added to the MARC representation
2455 of the item.
2456
2457 =cut
2458
2459 sub _marc_from_item_hash {
2460     my $item = shift;
2461     my $frameworkcode = shift;
2462     my $unlinked_item_subfields;
2463     if (@_) {
2464         $unlinked_item_subfields = shift;
2465     }
2466    
2467     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2468     # Also, don't emit a subfield if the underlying field is blank.
2469     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
2470                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
2471                                 : ()  } keys %{ $item } }; 
2472
2473     my $item_marc = MARC::Record->new();
2474     foreach my $item_field ( keys %{$mungeditem} ) {
2475         my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2476         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
2477         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2478         foreach my $value (@values){
2479             if ( my $field = $item_marc->field($tag) ) {
2480                     $field->add_subfields( $subfield => $value );
2481             } else {
2482                 my $add_subfields = [];
2483                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2484                     $add_subfields = $unlinked_item_subfields;
2485             }
2486             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2487             }
2488         }
2489     }
2490
2491     return $item_marc;
2492 }
2493
2494 =head2 _repack_item_errors
2495
2496 Add an error message hash generated by C<CheckItemPreSave>
2497 to a list of errors.
2498
2499 =cut
2500
2501 sub _repack_item_errors {
2502     my $item_sequence_num = shift;
2503     my $item_ref = shift;
2504     my $error_ref = shift;
2505
2506     my @repacked_errors = ();
2507
2508     foreach my $error_code (sort keys %{ $error_ref }) {
2509         my $repacked_error = {};
2510         $repacked_error->{'item_sequence'} = $item_sequence_num;
2511         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2512         $repacked_error->{'error_code'} = $error_code;
2513         $repacked_error->{'error_information'} = $error_ref->{$error_code};
2514         push @repacked_errors, $repacked_error;
2515     } 
2516
2517     return @repacked_errors;
2518 }
2519
2520 =head2 _get_unlinked_item_subfields
2521
2522   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2523
2524 =cut
2525
2526 sub _get_unlinked_item_subfields {
2527     my $original_item_marc = shift;
2528     my $frameworkcode = shift;
2529
2530     my $marcstructure = GetMarcStructure(1, $frameworkcode);
2531
2532     # assume that this record has only one field, and that that
2533     # field contains only the item information
2534     my $subfields = [];
2535     my @fields = $original_item_marc->fields();
2536     if ($#fields > -1) {
2537         my $field = $fields[0];
2538             my $tag = $field->tag();
2539         foreach my $subfield ($field->subfields()) {
2540             if (defined $subfield->[1] and
2541                 $subfield->[1] ne '' and
2542                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2543                 push @$subfields, $subfield->[0] => $subfield->[1];
2544             }
2545         }
2546     }
2547     return $subfields;
2548 }
2549
2550 =head2 _get_unlinked_subfields_xml
2551
2552   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2553
2554 =cut
2555
2556 sub _get_unlinked_subfields_xml {
2557     my $unlinked_item_subfields = shift;
2558
2559     my $xml;
2560     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2561         my $marc = MARC::Record->new();
2562         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2563         # used in the framework
2564         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2565         $marc->encoding("UTF-8");    
2566         $xml = $marc->as_xml("USMARC");
2567     }
2568
2569     return $xml;
2570 }
2571
2572 =head2 _parse_unlinked_item_subfields_from_xml
2573
2574   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2575
2576 =cut
2577
2578 sub  _parse_unlinked_item_subfields_from_xml {
2579     my $xml = shift;
2580     require C4::Charset;
2581     return unless defined $xml and $xml ne "";
2582     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2583     my $unlinked_subfields = [];
2584     my @fields = $marc->fields();
2585     if ($#fields > -1) {
2586         foreach my $subfield ($fields[0]->subfields()) {
2587             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2588         }
2589     }
2590     return $unlinked_subfields;
2591 }
2592
2593 =head2 GetAnalyticsCount
2594
2595   $count= &GetAnalyticsCount($itemnumber)
2596
2597 counts Usage of itemnumber in Analytical bibliorecords. 
2598
2599 =cut
2600
2601 sub GetAnalyticsCount {
2602     my ($itemnumber) = @_;
2603     require C4::Search;
2604
2605     ### ZOOM search here
2606     my $query;
2607     $query= "hi=".$itemnumber;
2608             my ($err,$res,$result) = C4::Search::SimpleSearch($query,0,10);
2609     return ($result);
2610 }
2611
2612 =head2 GetItemHolds
2613
2614   $holds = &GetItemHolds($biblionumber, $itemnumber);
2615
2616 This function return the count of holds with $biblionumber and $itemnumber
2617
2618 =cut
2619
2620 sub GetItemHolds {
2621     my ($biblionumber, $itemnumber) = @_;
2622     my $holds;
2623     my $dbh            = C4::Context->dbh;
2624     my $query          = "SELECT count(*)
2625         FROM  reserves
2626         WHERE biblionumber=? AND itemnumber=?";
2627     my $sth = $dbh->prepare($query);
2628     $sth->execute($biblionumber, $itemnumber);
2629     $holds = $sth->fetchrow;
2630     return $holds;
2631 }
2632
2633 =head2 SearchItemsByField
2634
2635     my $items = SearchItemsByField($field, $value);
2636
2637 SearchItemsByField will search for items on a specific given field.
2638 For instance you can search all items with a specific stocknumber like this:
2639
2640     my $items = SearchItemsByField('stocknumber', $stocknumber);
2641
2642 =cut
2643
2644 sub SearchItemsByField {
2645     my ($field, $value) = @_;
2646
2647     my $filters = {
2648         field => $field,
2649         query => $value,
2650     };
2651
2652     my ($results) = SearchItems($filters);
2653     return $results;
2654 }
2655
2656 sub _SearchItems_build_where_fragment {
2657     my ($filter) = @_;
2658
2659     my $dbh = C4::Context->dbh;
2660
2661     my $where_fragment;
2662     if (exists($filter->{conjunction})) {
2663         my (@where_strs, @where_args);
2664         foreach my $f (@{ $filter->{filters} }) {
2665             my $fragment = _SearchItems_build_where_fragment($f);
2666             if ($fragment) {
2667                 push @where_strs, $fragment->{str};
2668                 push @where_args, @{ $fragment->{args} };
2669             }
2670         }
2671         my $where_str = '';
2672         if (@where_strs) {
2673             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2674             $where_fragment = {
2675                 str => $where_str,
2676                 args => \@where_args,
2677             };
2678         }
2679     } else {
2680         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2681         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2682         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2683         my @operators = qw(= != > < >= <= like);
2684         my $field = $filter->{field};
2685         if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2686             my $op = $filter->{operator};
2687             my $query = $filter->{query};
2688
2689             if (!$op or (0 == grep /^$op$/, @operators)) {
2690                 $op = '='; # default operator
2691             }
2692
2693             my $column;
2694             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2695                 my $marcfield = $1;
2696                 my $marcsubfield = $2;
2697                 my ($kohafield) = $dbh->selectrow_array(q|
2698                     SELECT kohafield FROM marc_subfield_structure
2699                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2700                 |, undef, $marcfield, $marcsubfield);
2701
2702                 if ($kohafield) {
2703                     $column = $kohafield;
2704                 } else {
2705                     # MARC field is not linked to a DB field so we need to use
2706                     # ExtractValue on biblioitems.marcxml or
2707                     # items.more_subfields_xml, depending on the MARC field.
2708                     my $xpath;
2709                     my $sqlfield;
2710                     my ($itemfield) = GetMarcFromKohaField('items.itemnumber');
2711                     if ($marcfield eq $itemfield) {
2712                         $sqlfield = 'more_subfields_xml';
2713                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2714                     } else {
2715                         $sqlfield = 'marcxml';
2716                         if ($marcfield < 10) {
2717                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2718                         } else {
2719                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2720                         }
2721                     }
2722                     $column = "ExtractValue($sqlfield, '$xpath')";
2723                 }
2724             } else {
2725                 $column = $field;
2726             }
2727
2728             if (ref $query eq 'ARRAY') {
2729                 if ($op eq '=') {
2730                     $op = 'IN';
2731                 } elsif ($op eq '!=') {
2732                     $op = 'NOT IN';
2733                 }
2734                 $where_fragment = {
2735                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
2736                     args => $query,
2737                 };
2738             } else {
2739                 $where_fragment = {
2740                     str => "$column $op ?",
2741                     args => [ $query ],
2742                 };
2743             }
2744         }
2745     }
2746
2747     return $where_fragment;
2748 }
2749
2750 =head2 SearchItems
2751
2752     my ($items, $total) = SearchItems($filter, $params);
2753
2754 Perform a search among items
2755
2756 $filter is a reference to a hash which can be a filter, or a combination of filters.
2757
2758 A filter has the following keys:
2759
2760 =over 2
2761
2762 =item * field: the name of a SQL column in table items
2763
2764 =item * query: the value to search in this column
2765
2766 =item * operator: comparison operator. Can be one of = != > < >= <= like
2767
2768 =back
2769
2770 A combination of filters hash the following keys:
2771
2772 =over 2
2773
2774 =item * conjunction: 'AND' or 'OR'
2775
2776 =item * filters: array ref of filters
2777
2778 =back
2779
2780 $params is a reference to a hash that can contain the following parameters:
2781
2782 =over 2
2783
2784 =item * rows: Number of items to return. 0 returns everything (default: 0)
2785
2786 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2787                (default: 1)
2788
2789 =item * sortby: A SQL column name in items table to sort on
2790
2791 =item * sortorder: 'ASC' or 'DESC'
2792
2793 =back
2794
2795 =cut
2796
2797 sub SearchItems {
2798     my ($filter, $params) = @_;
2799
2800     $filter //= {};
2801     $params //= {};
2802     return unless ref $filter eq 'HASH';
2803     return unless ref $params eq 'HASH';
2804
2805     # Default parameters
2806     $params->{rows} ||= 0;
2807     $params->{page} ||= 1;
2808     $params->{sortby} ||= 'itemnumber';
2809     $params->{sortorder} ||= 'ASC';
2810
2811     my ($where_str, @where_args);
2812     my $where_fragment = _SearchItems_build_where_fragment($filter);
2813     if ($where_fragment) {
2814         $where_str = $where_fragment->{str};
2815         @where_args = @{ $where_fragment->{args} };
2816     }
2817
2818     my $dbh = C4::Context->dbh;
2819     my $query = q{
2820         SELECT SQL_CALC_FOUND_ROWS items.*
2821         FROM items
2822           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2823           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2824     };
2825     if (defined $where_str and $where_str ne '') {
2826         $query .= qq{ WHERE $where_str };
2827     }
2828
2829     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2830     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2831     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2832     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2833         ? $params->{sortby} : 'itemnumber';
2834     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2835     $query .= qq{ ORDER BY $sortby $sortorder };
2836
2837     my $rows = $params->{rows};
2838     my @limit_args;
2839     if ($rows > 0) {
2840         my $offset = $rows * ($params->{page}-1);
2841         $query .= qq { LIMIT ?, ? };
2842         push @limit_args, $offset, $rows;
2843     }
2844
2845     my $sth = $dbh->prepare($query);
2846     my $rv = $sth->execute(@where_args, @limit_args);
2847
2848     return unless ($rv);
2849     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2850
2851     return ($sth->fetchall_arrayref({}), $total_rows);
2852 }
2853
2854
2855 =head1  OTHER FUNCTIONS
2856
2857 =head2 _find_value
2858
2859   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2860
2861 Find the given $subfield in the given $tag in the given
2862 MARC::Record $record.  If the subfield is found, returns
2863 the (indicators, value) pair; otherwise, (undef, undef) is
2864 returned.
2865
2866 PROPOSITION :
2867 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2868 I suggest we export it from this module.
2869
2870 =cut
2871
2872 sub _find_value {
2873     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2874     my @result;
2875     my $indicator;
2876     if ( $tagfield < 10 ) {
2877         if ( $record->field($tagfield) ) {
2878             push @result, $record->field($tagfield)->data();
2879         } else {
2880             push @result, "";
2881         }
2882     } else {
2883         foreach my $field ( $record->field($tagfield) ) {
2884             my @subfields = $field->subfields();
2885             foreach my $subfield (@subfields) {
2886                 if ( @$subfield[0] eq $insubfield ) {
2887                     push @result, @$subfield[1];
2888                     $indicator = $field->indicator(1) . $field->indicator(2);
2889                 }
2890             }
2891         }
2892     }
2893     return ( $indicator, @result );
2894 }
2895
2896
2897 =head2 PrepareItemrecordDisplay
2898
2899   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2900
2901 Returns a hash with all the fields for Display a given item data in a template
2902
2903 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2904
2905 =cut
2906
2907 sub PrepareItemrecordDisplay {
2908
2909     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2910
2911     my $dbh = C4::Context->dbh;
2912     $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2913     my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2914     my $tagslib = &GetMarcStructure( 1, $frameworkcode );
2915
2916     # return nothing if we don't have found an existing framework.
2917     return q{} unless $tagslib;
2918     my $itemrecord;
2919     if ($itemnum) {
2920         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2921     }
2922     my @loop_data;
2923
2924     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2925     my $query = qq{
2926         SELECT authorised_value,lib FROM authorised_values
2927     };
2928     $query .= qq{
2929         LEFT JOIN authorised_values_branches ON ( id = av_id )
2930     } if $branch_limit;
2931     $query .= qq{
2932         WHERE category = ?
2933     };
2934     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2935     $query .= qq{ ORDER BY lib};
2936     my $authorised_values_sth = $dbh->prepare( $query );
2937     foreach my $tag ( sort keys %{$tagslib} ) {
2938         my $previous_tag = '';
2939         if ( $tag ne '' ) {
2940
2941             # loop through each subfield
2942             my $cntsubf;
2943             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2944                 next if ( subfield_is_koha_internal_p($subfield) );
2945                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2946                 my %subfield_data;
2947                 $subfield_data{tag}           = $tag;
2948                 $subfield_data{subfield}      = $subfield;
2949                 $subfield_data{countsubfield} = $cntsubf++;
2950                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2951                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2952
2953                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2954                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2955                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2956                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2957                 $subfield_data{hidden}     = "display:none"
2958                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2959                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2960                 my ( $x, $defaultvalue );
2961                 if ($itemrecord) {
2962                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2963                 }
2964                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2965                 if ( !defined $defaultvalue ) {
2966                     $defaultvalue = q||;
2967                 } else {
2968                     $defaultvalue =~ s/"/&quot;/g;
2969                 }
2970
2971                 # search for itemcallnumber if applicable
2972                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2973                     && C4::Context->preference('itemcallnumber') ) {
2974                     my $CNtag      = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2975                     my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2976                     if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2977                         $defaultvalue = $field->subfield($CNsubfield);
2978                     }
2979                 }
2980                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2981                     && $defaultvalues
2982                     && $defaultvalues->{'callnumber'} ) {
2983                     if( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ){
2984                         # if the item record exists, only use default value if the item has no callnumber
2985                         $defaultvalue = $defaultvalues->{callnumber};
2986                     } elsif ( !$itemrecord and $defaultvalues ) {
2987                         # if the item record *doesn't* exists, always use the default value
2988                         $defaultvalue = $defaultvalues->{callnumber};
2989                     }
2990                 }
2991                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2992                     && $defaultvalues
2993                     && $defaultvalues->{'branchcode'} ) {
2994                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2995                         $defaultvalue = $defaultvalues->{branchcode};
2996                     }
2997                 }
2998                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2999                     && $defaultvalues
3000                     && $defaultvalues->{'location'} ) {
3001
3002                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
3003                         # if the item record exists, only use default value if the item has no locationr
3004                         $defaultvalue = $defaultvalues->{location};
3005                     } elsif ( !$itemrecord and $defaultvalues ) {
3006                         # if the item record *doesn't* exists, always use the default value
3007                         $defaultvalue = $defaultvalues->{location};
3008                     }
3009                 }
3010                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
3011                     my @authorised_values;
3012                     my %authorised_lib;
3013
3014                     # builds list, depending on authorised value...
3015                     #---- branch
3016                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
3017                         if (   ( C4::Context->preference("IndependentBranches") )
3018                             && !C4::Context->IsSuperLibrarian() ) {
3019                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
3020                             $sth->execute( C4::Context->userenv->{branch} );
3021                             push @authorised_values, ""
3022                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3023                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
3024                                 push @authorised_values, $branchcode;
3025                                 $authorised_lib{$branchcode} = $branchname;
3026                             }
3027                         } else {
3028                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
3029                             $sth->execute;
3030                             push @authorised_values, ""
3031                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3032                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
3033                                 push @authorised_values, $branchcode;
3034                                 $authorised_lib{$branchcode} = $branchname;
3035                             }
3036                         }
3037
3038                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
3039                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
3040                             $defaultvalue = $defaultvalues->{branchcode};
3041                         }
3042
3043                         #----- itemtypes
3044                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
3045                         my $itemtypes = GetItemTypes( style => 'array' );
3046                         push @authorised_values, ""
3047                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3048                         for my $itemtype ( @$itemtypes ) {
3049                             push @authorised_values, $itemtype->{itemtype};
3050                             $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
3051                         }
3052                         #---- class_sources
3053                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
3054                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3055
3056                         my $class_sources = GetClassSources();
3057                         my $default_source = C4::Context->preference("DefaultClassificationSource");
3058
3059                         foreach my $class_source (sort keys %$class_sources) {
3060                             next unless $class_sources->{$class_source}->{'used'} or
3061                                         ($class_source eq $default_source);
3062                             push @authorised_values, $class_source;
3063                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
3064                         }
3065
3066                         $defaultvalue = $default_source;
3067
3068                         #---- "true" authorised value
3069                     } else {
3070                         $authorised_values_sth->execute(
3071                             $tagslib->{$tag}->{$subfield}->{authorised_value},
3072                             $branch_limit ? $branch_limit : ()
3073                         );
3074                         push @authorised_values, ""
3075                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3076                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
3077                             push @authorised_values, $value;
3078                             $authorised_lib{$value} = $lib;
3079                         }
3080                     }
3081                     $subfield_data{marc_value} = {
3082                         type    => 'select',
3083                         values  => \@authorised_values,
3084                         default => "$defaultvalue",
3085                         labels  => \%authorised_lib,
3086                     };
3087                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
3088                 # it is a plugin
3089                     require Koha::FrameworkPlugin;
3090                     my $plugin = Koha::FrameworkPlugin->new({
3091                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
3092                         item_style => 1,
3093                     });
3094                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
3095                     $plugin->build( $pars );
3096                     if( !$plugin->errstr ) {
3097                         #TODO Move html to template; see report 12176/13397
3098                         my $tab= $plugin->noclick? '-1': '';
3099                         my $class= $plugin->noclick? ' disabled': '';
3100                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
3101                         $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
3102                     } else {
3103                         warn $plugin->errstr;
3104                         $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" />); # supply default input form
3105                     }
3106                 }
3107                 elsif ( $tag eq '' ) {       # it's an hidden field
3108                     $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
3109                 }
3110                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
3111                     $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
3112                 }
3113                 elsif ( length($defaultvalue) > 100
3114                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
3115                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
3116                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
3117                                   500 <= $tag && $tag < 600                     )
3118                           ) {
3119                     # oversize field (textarea)
3120                     $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255">$defaultvalue</textarea>\n");
3121                 } else {
3122                     $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
3123                 }
3124                 push( @loop_data, \%subfield_data );
3125             }
3126         }
3127     }
3128     my $itemnumber;
3129     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
3130         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
3131     }
3132     return {
3133         'itemtagfield'    => $itemtagfield,
3134         'itemtagsubfield' => $itemtagsubfield,
3135         'itemnumber'      => $itemnumber,
3136         'iteminformation' => \@loop_data
3137     };
3138 }
3139
3140 1;