[3.0.x] (bug #4263) fix the edition of items with repeatable subfields
[koha.git] / C4 / Items.pm
index 39b24f2..d19caf9 100644 (file)
@@ -19,8 +19,6 @@ package C4::Items;
 
 use strict;
 
-require Exporter;
-
 use C4::Context;
 use C4::Koha;
 use C4::Biblio;
@@ -30,37 +28,43 @@ use C4::ClassSource;
 use C4::Log;
 use C4::Branch;
 require C4::Reserves;
+use C4::Charset;
 
 use vars qw($VERSION @ISA @EXPORT);
 
-my $VERSION = 3.00;
-
-@ISA = qw( Exporter );
-
-# function exports
-@EXPORT = qw(
-    GetItem
-    AddItemFromMarc
-    AddItem
-    ModItemFromMarc
-    ModItem
-    ModDateLastSeen
-    ModItemTransfer
-    DelItem
-
-    CheckItemPreSave
-
-    GetItemStatus
-    GetItemLocation
-    GetLostItems
-    GetItemsForInventory
-    GetItemsCount
-    GetItemInfosOf
-    GetItemsByBiblioitemnumber
-    GetItemsInfo
-    get_itemnumbers_of
-    GetItemnumberFromBarcode
-);
+BEGIN {
+    $VERSION = 3.01;
+
+       require Exporter;
+    @ISA = qw( Exporter );
+
+    # function exports
+    @EXPORT = qw(
+        GetItem
+        AddItemFromMarc
+        AddItem
+        AddItemBatchFromMarc
+        ModItemFromMarc
+        ModItem
+        ModDateLastSeen
+        ModItemTransfer
+        DelItem
+        DelItemCheck
+    
+        CheckItemPreSave
+    
+        GetItemStatus
+        GetItemLocation
+        GetLostItems
+        GetItemsForInventory
+        GetItemsCount
+        GetItemInfosOf
+        GetItemsByBiblioitemnumber
+        GetItemsInfo
+        get_itemnumbers_of
+        GetItemnumberFromBarcode
+    );
+}
 
 =head1 NAME
 
@@ -107,35 +111,47 @@ of C<C4::Items>
 
 =over 4
 
-$item = GetItem($itemnumber,$barcode);
+$item = GetItem($itemnumber,$barcode,$serial);
 
 =back
 
 Return item information, for a given itemnumber or barcode.
 The return value is a hashref mapping item column
-names to values.
+names to values.  If C<$serial> is true, include serial publication data.
 
 =cut
 
 sub GetItem {
-    my ($itemnumber,$barcode) = @_;
+    my ($itemnumber,$barcode, $serial) = @_;
     my $dbh = C4::Context->dbh;
+       my $data;
     if ($itemnumber) {
         my $sth = $dbh->prepare("
             SELECT * FROM items 
             WHERE itemnumber = ?");
         $sth->execute($itemnumber);
-        my $data = $sth->fetchrow_hashref;
-        return $data;
+        $data = $sth->fetchrow_hashref;
     } else {
         my $sth = $dbh->prepare("
             SELECT * FROM items 
             WHERE barcode = ?"
             );
-        $sth->execute($barcode);
-        my $data = $sth->fetchrow_hashref;
-        return $data;
+        $sth->execute($barcode);               
+        $data = $sth->fetchrow_hashref;
+    }
+    if ( $serial) {      
+    my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
+        $ssth->execute($data->{'itemnumber'}) ;
+        ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
+               warn $data->{'serialseq'} , $data->{'publisheddate'};
     }
+       #if we don't have an items.itype, use biblioitems.itemtype.
+       if( ! $data->{'itype'} ) {
+               my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems  WHERE biblionumber = ?");
+               $sth->execute($data->{'biblionumber'});
+               ($data->{'itype'}) = $sth->fetchrow_array;
+       }
+    return $data;
 }    # sub GetItem
 
 =head2 AddItemFromMarc
@@ -158,9 +174,13 @@ sub AddItemFromMarc {
 
     # parse item hash from MARC
     my $frameworkcode = GetFrameworkCode( $biblionumber );
-    my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
-
-    return AddItem($item, $biblionumber, $dbh, $frameworkcode);
+       my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
+       
+       my $localitemmarc=MARC::Record->new;
+       $localitemmarc->append_fields($source_item_marc->field($itemtag));
+    my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode ,'items');
+    my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
+    return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
 }
 
 =head2 AddItem
@@ -168,17 +188,24 @@ sub AddItemFromMarc {
 =over 4
 
 my ($biblionumber, $biblioitemnumber, $itemnumber) 
-    = AddItem($item, $biblionumber[, $dbh, $frameworkcode]);
+    = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
 
 =back
 
 Given a hash containing item column names as keys,
 create a new Koha item record.
 
-The two optional parameters (C<$dbh> and C<$frameworkcode>)
+The first two optional parameters (C<$dbh> and C<$frameworkcode>)
 do not need to be supplied for general use; they exist
 simply to allow them to be picked up from AddItemFromMarc.
 
+The final optional parameter, C<$unlinked_item_subfields>, contains
+an arrayref containing subfields present in the original MARC
+representation of the item (e.g., from the item editor) that are
+not mapped to C<items> columns directly but should instead
+be stored in C<items.more_subfields_xml> and included in 
+the biblio items tag for display and indexing.
+
 =cut
 
 sub AddItem {
@@ -187,6 +214,10 @@ sub AddItem {
 
     my $dbh           = @_ ? shift : C4::Context->dbh;
     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
+    my $unlinked_item_subfields;  
+    if (@_) {
+        $unlinked_item_subfields = shift
+    };
 
     # needs old biblionumber and biblioitemnumber
     $item->{'biblionumber'} = $biblionumber;
@@ -196,20 +227,133 @@ sub AddItem {
 
     _set_defaults_for_add($item);
     _set_derived_columns_for_add($item);
+    $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
     # FIXME - checks here
-    my ( $itemnumber, $error ) = _koha_new_item( $dbh, $item, $item->{barcode} );
+    unless ( $item->{itype} ) {  # default to biblioitem.itemtype if no itype
+        my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
+        $itype_sth->execute( $item->{'biblionumber'} );
+        ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
+    }
+
+       my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
     $item->{'itemnumber'} = $itemnumber;
 
     # create MARC tag representing item and add to bib
-    my $new_item_marc = _marc_from_item_hash($item, $frameworkcode);
+    my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
     _add_item_field_to_biblio($new_item_marc, $item->{'biblionumber'}, $frameworkcode );
    
-    logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item") 
-        if C4::Context->preference("CataloguingLog");
+    logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
     
     return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
 }
 
+=head2 AddItemBatchFromMarc
+
+=over 4
+
+($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, $biblionumber, $biblioitemnumber, $frameworkcode);
+
+=back
+
+Efficiently create item records from a MARC biblio record with
+embedded item fields.  This routine is suitable for batch jobs.
+
+This API assumes that the bib record has already been
+saved to the C<biblio> and C<biblioitems> tables.  It does
+not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
+are populated, but it will do so via a call to ModBibiloMarc.
+
+The goal of this API is to have a similar effect to using AddBiblio
+and AddItems in succession, but without inefficient repeated
+parsing of the MARC XML bib record.
+
+This function returns an arrayref of new itemsnumbers and an arrayref of item
+errors encountered during the processing.  Each entry in the errors
+list is a hashref containing the following keys:
+
+=over 2
+
+=item item_sequence
+
+Sequence number of original item tag in the MARC record.
+
+=item item_barcode
+
+Item barcode, provide to assist in the construction of
+useful error messages.
+
+=item error_condition
+
+Code representing the error condition.  Can be 'duplicate_barcode',
+'invalid_homebranch', or 'invalid_holdingbranch'.
+
+=item error_information
+
+Additional information appropriate to the error condition.
+
+=back
+
+=cut
+
+sub AddItemBatchFromMarc {
+    my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
+    my $error;
+    my @itemnumbers = ();
+    my @errors = ();
+    my $dbh = C4::Context->dbh;
+
+    # loop through the item tags and start creating items
+    my @bad_item_fields = ();
+    my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
+    my $item_sequence_num = 0;
+    ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
+        $item_sequence_num++;
+        # we take the item field and stick it into a new
+        # MARC record -- this is required so far because (FIXME)
+        # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
+        # and there is no TransformMarcFieldToKoha
+        my $temp_item_marc = MARC::Record->new();
+        $temp_item_marc->append_fields($item_field);
+    
+        # add biblionumber and biblioitemnumber
+        my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' );
+        my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
+        $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
+        $item->{'biblionumber'} = $biblionumber;
+        $item->{'biblioitemnumber'} = $biblioitemnumber;
+
+        # check for duplicate barcode
+        my %item_errors = CheckItemPreSave($item);
+        if (%item_errors) {
+            push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
+            push @bad_item_fields, $item_field;
+            next ITEMFIELD;
+        }
+
+        _set_defaults_for_add($item);
+        _set_derived_columns_for_add($item);
+        my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
+        warn $error if $error;
+        push @itemnumbers, $itemnumber; # FIXME not checking error
+        $item->{'itemnumber'} = $itemnumber;
+
+        logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
+
+        my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
+        $item_field->replace_with($new_item_marc->field($itemtag));
+    }
+
+    # remove any MARC item fields for rejected items
+    foreach my $item_field (@bad_item_fields) {
+        $record->delete_field($item_field);
+    }
+
+    # update the MARC biblio
+    $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
+
+    return (\@itemnumbers, \@errors);
+}
+
 =head2 ModItemFromMarc
 
 =over 4
@@ -223,8 +367,49 @@ C<MARC::Record> object containing an embedded item field.
 This API is meant for the use of C<additem.pl>; for 
 other purposes, C<ModItem> should be used.
 
+This function uses the hash %default_values_for_mod_from_marc,
+which contains default values for item fields to
+apply when modifying an item.  This is needed beccause
+if an item field's value is cleared, TransformMarcToKoha
+does not include the column in the
+hash that's passed to ModItem, which without
+use of this hash makes it impossible to clear
+an item field's value.  See bug 2466.
+
+Note that only columns that can be directly
+changed from the cataloging and serials
+item editors are included in this hash.
+
 =cut
 
+my %default_values_for_mod_from_marc = (
+    barcode              => undef, 
+    booksellerid         => undef, 
+    ccode                => undef, 
+    'items.cn_source'    => undef, 
+    copynumber           => undef, 
+    damaged              => 0,
+    dateaccessioned      => undef, 
+    enumchron            => undef, 
+    holdingbranch        => undef, 
+    homebranch           => undef, 
+    itemcallnumber       => undef, 
+    itemlost             => 0,
+    itemnotes            => undef, 
+    itype                => undef, 
+    location             => undef, 
+    materials            => undef, 
+    notforloan           => 0,
+    paidfor              => undef, 
+    price                => undef, 
+    replacementprice     => undef, 
+    replacementpricedate => undef, 
+    restricted           => undef, 
+    stack                => undef, 
+    uri                  => undef, 
+    wthdrawn             => 0,
+);
+
 sub ModItemFromMarc {
     my $item_marc = shift;
     my $biblionumber = shift;
@@ -232,16 +417,24 @@ sub ModItemFromMarc {
 
     my $dbh = C4::Context->dbh;
     my $frameworkcode = GetFrameworkCode( $biblionumber );
-    my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode );
-   
-    return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode); 
+       my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
+       
+       my $localitemmarc=MARC::Record->new;
+       $localitemmarc->append_fields($item_marc->field($itemtag));
+    my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode, 'items');
+    foreach my $item_field (keys %default_values_for_mod_from_marc) {
+        $item->{$item_field} = $default_values_for_mod_from_marc{$item_field} unless exists $item->{$item_field};
+    }
+    my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
+
+    return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
 }
 
 =head2 ModItem
 
 =over 4
 
-ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
+ModItem({ column => $newvalue }, $biblionumber, $itemnumber[, $original_item_marc]);
 
 =back
 
@@ -252,6 +445,13 @@ The first argument is a hashref mapping from item column
 names to the new values.  The second and third arguments
 are the biblionumber and itemnumber, respectively.
 
+The fourth, optional parameter, C<$unlinked_item_subfields>, contains
+an arrayref containing subfields present in the original MARC
+representation of the item (e.g., from the item editor) that are
+not mapped to C<items> columns directly but should instead
+be stored in C<items.more_subfields_xml> and included in 
+the biblio items tag for display and indexing.
+
 If one of the changed columns is used to calculate
 the derived value of a column such as C<items.cn_sort>, 
 this routine will perform the necessary calculation
@@ -271,8 +471,14 @@ sub ModItem {
 
     my $dbh           = @_ ? shift : C4::Context->dbh;
     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
+    
+    my $unlinked_item_subfields;  
+    if (@_) {
+        $unlinked_item_subfields = shift;
+        $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
+    };
 
-    $item->{'itemnumber'} = $itemnumber;
+    $item->{'itemnumber'} = $itemnumber or return undef;
     _set_derived_columns_for_mod($item);
     _do_column_fixes_for_mod($item);
     # FIXME add checks
@@ -283,15 +489,20 @@ sub ModItem {
     # it should be a separate function)
 
     # update items table
-    _koha_modify_item($dbh, $item);
+    _koha_modify_item($item);
 
     # update biblio MARC XML
-    my $whole_item = GetItem($itemnumber);
-    my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode);
-    _replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
+    my $whole_item = GetItem($itemnumber) or die "FAILED GetItem($itemnumber)";
+
+    unless (defined $unlinked_item_subfields) {
+        $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'});
+    }
+    my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode, $unlinked_item_subfields) 
+        or die "FAILED _marc_from_item_hash($whole_item, $frameworkcode)";
     
-    logaction(C4::Context->userenv->{'number'},"CATALOGUING","MODIFY",$itemnumber,$new_item_marc->as_formatted)
-        if C4::Context->preference("CataloguingLog");
+    _replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
+       ($new_item_marc       eq '0') and die "$new_item_marc is '0', not hashref";  # logaction line would crash anyway
+    logaction("CATALOGUING", "MODIFY", $itemnumber, $new_item_marc->as_formatted) if C4::Context->preference("CataloguingLog");
 }
 
 =head2 ModItemTransfer
@@ -381,8 +592,48 @@ sub DelItem {
         }
     }
     &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
-    &logaction(C4::Context->userenv->{'number'},"CATALOGUING","DELETE",$itemnumber,"item") 
-        if C4::Context->preference("CataloguingLog");
+    logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
+}
+
+=head2 DelItemCheck
+
+=over 4
+
+DelItemCheck($dbh, $biblionumber, $itemnumber);
+
+=back
+
+Exported function (core API) for deleting an item record in Koha if there no current issue.
+
+=cut
+
+sub DelItemCheck {
+    my ( $dbh, $biblionumber, $itemnumber ) = @_;
+    my $error;
+
+    # check that there is no issue on this item before deletion.
+    my $sth=$dbh->prepare("select * from issues i where i.itemnumber=?");
+    $sth->execute($itemnumber);
+
+    my $onloan=$sth->fetchrow;
+
+    $sth->finish();
+    if ($onloan){
+        $error = "book_on_loan" 
+    }else{
+        # check it doesnt have a waiting reserve
+        $sth=$dbh->prepare("SELECT * FROM reserves WHERE found = 'W' AND itemnumber = ?");
+        $sth->execute($itemnumber);
+        my $reserve=$sth->fetchrow;
+        $sth->finish();
+        if ($reserve){
+            $error = "book_reserved";
+        }else{
+            DelItem($dbh, $biblionumber, $itemnumber);
+            return 1;
+        }
+    }
+    return $error;
 }
 
 =head2 CheckItemPreSave
@@ -690,28 +941,35 @@ sub GetItemLocation {
 
 =over 4
 
-$items = GetLostItems($where,$orderby);
+$items = GetLostItems( $where, $orderby );
 
 =back
 
-This function get the items lost into C<$items>.
+This function gets a list of lost items.
 
 =over 2
 
 =item input:
+
 C<$where> is a hashref. it containts a field of the items table as key
-and the value to match as value.
-C<$orderby> is a field of the items table.
+and the value to match as value. For example:
+
+{ barcode    => 'abc123',
+  homebranch => 'CPL',    }
+
+C<$orderby> is a field of the items table by which the resultset
+should be orderd.
 
 =item return:
-C<$items> is a reference to an array full of hasref which keys are items' table column.
+
+C<$items> is a reference to an array full of hashrefs with columns
+from the "items" table as keys.
 
 =item usage in the perl script:
 
-my %where;
-$where{barcode} = 0001548;
-my $items = GetLostItems( \%where, "homebranch" );
-$template->param(itemsloop => $items);
+my $where = { barcode => '0001548' };
+my $items = GetLostItems( $where, "homebranch" );
+$template->param( itemsloop => $items );
 
 =back
 
@@ -726,75 +984,116 @@ sub GetLostItems {
     my $query   = "
         SELECT *
         FROM   items
-        WHERE  itemlost IS NOT NULL
-          AND  itemlost <> 0
+            LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
+            LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
+            LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
+        WHERE
+               authorised_values.category = 'LOST'
+               AND itemlost IS NOT NULL
+               AND itemlost <> 0
     ";
+    my @query_parameters;
     foreach my $key (keys %$where) {
-        $query .= " AND " . $key . " LIKE '%" . $where->{$key} . "%'";
+        $query .= " AND $key LIKE ?";
+        push @query_parameters, "%$where->{$key}%";
+    }
+    my @ordervalues = qw/title author homebranch itype barcode price replacementprice lib datelastseen location/;
+    
+    if ( defined $orderby && grep($orderby, @ordervalues)) {
+        $query .= ' ORDER BY '.$orderby;
     }
-    $query .= " ORDER BY ".$orderby if defined $orderby;
 
     my $sth = $dbh->prepare($query);
-    $sth->execute;
-    my @items;
+    $sth->execute( @query_parameters );
+    my $items = [];
     while ( my $row = $sth->fetchrow_hashref ){
-        push @items, $row;
+        push @$items, $row;
     }
-    return \@items;
+    return $items;
 }
 
 =head2 GetItemsForInventory
 
 =over 4
 
-$itemlist = GetItemsForInventory($minlocation,$maxlocation,$datelastseen,$offset,$size)
+$itemlist = GetItemsForInventory($minlocation, $maxlocation, $location, $itemtype $datelastseen, $branch, $offset, $size);
 
 =back
 
 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
 
-The sub returns a list of hashes, containing itemnumber, author, title, barcode & item callnumber.
-It is ordered by callnumber,title.
+The sub returns a reference to a list of hashes, each containing
+itemnumber, author, title, barcode, item callnumber, and date last
+seen. It is ordered by callnumber then title.
 
-The minlocation & maxlocation parameters are used to specify a range of item callnumbers
+The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
 the datelastseen can be used to specify that you want to see items not seen since a past date only.
 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
 
 =cut
 
 sub GetItemsForInventory {
-    my ( $minlocation, $maxlocation,$location, $datelastseen, $branch, $offset, $size ) = @_;
+    my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branch, $offset, $size ) = @_;
     my $dbh = C4::Context->dbh;
-    my $sth;
+    my ( @bind_params, @where_strings );
+
+    my $query = <<'END_SQL';
+SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, datelastseen
+FROM items
+  LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
+  LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
+END_SQL
+
+    if ($minlocation) {
+        push @where_strings, 'itemcallnumber >= ?';
+        push @bind_params, $minlocation;
+    }
+
+    if ($maxlocation) {
+        push @where_strings, 'itemcallnumber <= ?';
+        push @bind_params, $maxlocation;
+    }
+
     if ($datelastseen) {
-        $datelastseen=format_date_in_iso($datelastseen);  
-        my $query =
-                "SELECT itemnumber,barcode,itemcallnumber,title,author,biblio.biblionumber,datelastseen
-                 FROM items
-                   LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber 
-                 WHERE itemcallnumber>= ?
-                   AND itemcallnumber <=?
-                   AND (datelastseen< ? OR datelastseen IS NULL)";
-        $query.= " AND items.location=".$dbh->quote($location) if $location;
-        $query.= " AND items.homebranch=".$dbh->quote($branch) if $branch;
-        $query .= " ORDER BY itemcallnumber,title";
-        $sth = $dbh->prepare($query);
-        $sth->execute( $minlocation, $maxlocation, $datelastseen );
+        $datelastseen = format_date_in_iso($datelastseen);  
+        push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
+        push @bind_params, $datelastseen;
     }
-    else {
-        my $query ="
-                SELECT itemnumber,barcode,itemcallnumber,biblio.biblionumber,title,author,datelastseen
-                FROM items 
-                  LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber 
-                WHERE itemcallnumber>= ?
-                  AND itemcallnumber <=?";
-        $query.= " AND items.location=".$dbh->quote($location) if $location;
-        $query.= " AND items.homebranch=".$dbh->quote($branch) if $branch;
-        $query .= " ORDER BY itemcallnumber,title";
-        $sth = $dbh->prepare($query);
-        $sth->execute( $minlocation, $maxlocation );
+
+    if ( $location ) {
+        push @where_strings, 'items.location = ?';
+        push @bind_params, $location;
+    }
+    
+    if ( $branch ) {
+        push @where_strings, 'items.homebranch = ?';
+        push @bind_params, $branch;
     }
+    
+    if ( $itemtype ) {
+        push @where_strings, 'biblioitems.itemtype = ?';
+        push @bind_params, $itemtype;
+    }
+    if ( $ignoreissued) {
+        $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
+        push @where_strings, 'issues.date_due IS NULL';
+    }
+
+    if ( $ignoreissued) {
+        $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
+        push @where_strings, 'issues.date_due IS NULL';
+    }
+
+    if ( @where_strings ) {
+        $query .= 'WHERE ';
+        $query .= join ' AND ', @where_strings;
+    }
+    $query .= ' ORDER BY itemcallnumber, title';
+    my $sth = $dbh->prepare($query);
+    $sth->execute( @bind_params );
+
     my @results;
+    $size--;
     while ( my $row = $sth->fetchrow_hashref ) {
         $offset-- if ($offset);
         $row->{datelastseen}=format_date($row->{datelastseen});
@@ -876,7 +1175,6 @@ sub GetItemsByBiblioitemnumber {
         # Foreach item, get circulation information
         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
                                    WHERE itemnumber = ?
-                                   AND returndate is NULL
                                    AND issues.borrowernumber = borrowers.borrowernumber"
         );
         $sth2->execute( $data->{'itemnumber'} );
@@ -892,9 +1190,8 @@ sub GetItemsByBiblioitemnumber {
         }    # else         
         $sth2->finish;
         # Find the last 3 people who borrowed this item.                  
-        my $query2 = "SELECT * FROM issues, borrowers WHERE itemnumber = ?
-                      AND issues.borrowernumber = borrowers.borrowernumber
-                      AND returndate is not NULL
+        my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
+                      AND old_issues.borrowernumber = borrowers.borrowernumber
                       ORDER BY returndate desc,timestamp desc LIMIT 3";
         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
@@ -965,27 +1262,43 @@ If this is set, it is set to C<One Order>.
 sub GetItemsInfo {
     my ( $biblionumber, $type ) = @_;
     my $dbh   = C4::Context->dbh;
-    my $query = "SELECT *,items.notforloan as itemnotforloan
-                 FROM items 
-                 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
-                 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
-    $query .=  (C4::Context->preference('item-level_itypes')) ?
-                     " LEFT JOIN itemtypes on items.itype = itemtypes.itemtype "
-                    : " LEFT JOIN itemtypes on biblioitems.itemtype = itemtypes.itemtype ";
-    $query .= "WHERE items.biblionumber = ? ORDER BY items.dateaccessioned desc" ;
+    # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
+    my $query = "
+    SELECT items.*,
+           biblio.*,
+           biblioitems.volume,
+           biblioitems.number,
+           biblioitems.itemtype,
+           biblioitems.isbn,
+           biblioitems.issn,
+           biblioitems.publicationyear,
+           biblioitems.publishercode,
+           biblioitems.volumedate,
+           biblioitems.volumedesc,
+           biblioitems.lccn,
+           biblioitems.url,
+           items.notforloan as itemnotforloan,
+           itemtypes.description
+     FROM items
+     LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
+     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
+     LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
+     . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
+    $query .= " WHERE items.biblionumber = ? ORDER BY items.dateaccessioned desc" ;
     my $sth = $dbh->prepare($query);
     $sth->execute($biblionumber);
     my $i = 0;
     my @results;
-    my ( $date_due, $count_reserves );
+    my $serial;
 
     my $isth    = $dbh->prepare(
         "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
         FROM   issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
-        WHERE  itemnumber = ?
-            AND returndate IS NULL"
+        WHERE  itemnumber = ?"
        );
-    while ( my $data = $sth->fetchrow_hashref ) {
+       my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? "); 
+       while ( my $data = $sth->fetchrow_hashref ) {
+           my $count_reserves;
         my $datedue = '';
         $isth->execute( $data->{'itemnumber'} );
         if ( my $idata = $isth->fetchrow_hashref ) {
@@ -1001,7 +1314,12 @@ sub GetItemsInfo {
         }
         }
         }
-        if ( $datedue eq '' ) {
+               if ( $data->{'serial'}) {       
+                       $ssth->execute($data->{'itemnumber'}) ;
+                       ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
+                       $serial = 1;
+        }
+               if ( $datedue eq '' ) {
             my ( $restype, $reserves ) =
               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
             if ($restype) {
@@ -1009,7 +1327,7 @@ sub GetItemsInfo {
             }
         }
         $isth->finish;
-
+        $ssth->finish;
         #get branch information.....
         my $bsth = $dbh->prepare(
             "SELECT * FROM branches WHERE branchcode = ?
@@ -1041,8 +1359,9 @@ sub GetItemsInfo {
             $sthnflstatus->execute( $authorised_valuecode,
                 $data->{itemnotforloan} );
             my ($lib) = $sthnflstatus->fetchrow;
-            $data->{notforloan} = $lib;
+            $data->{notforloanvalue} = $lib;
         }
+               $data->{itypenotforloan} = $data->{notforloan} if (C4::Context->preference('item-level_itypes'));
 
         # my stack procedures
         my $stackstatus = $dbh->prepare(
@@ -1067,10 +1386,11 @@ sub GetItemsInfo {
             $data->{stack} = $lib;
         }
         # Find the last 3 people who borrowed this item.
-        my $sth2 = $dbh->prepare("SELECT * FROM issues,borrowers
+        my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
                                     WHERE itemnumber = ?
-                                    AND issues.borrowernumber = borrowers.borrowernumber
-                                    AND returndate IS NOT NULL LIMIT 3");
+                                    AND old_issues.borrowernumber = borrowers.borrowernumber
+                                    ORDER BY returndate DESC
+                                    LIMIT 3");
         $sth2->execute($data->{'itemnumber'});
         my $ii = 0;
         while (my $data2 = $sth2->fetchrow_hashref()) {
@@ -1083,9 +1403,11 @@ sub GetItemsInfo {
         $results[$i] = $data;
         $i++;
     }
-    $sth->finish;
-
-    return (@results);
+       if($serial) {
+               return( sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results );
+       } else {
+       return (@results);
+       }
 }
 
 =head2 get_itemnumbers_of
@@ -1148,6 +1470,106 @@ sub GetItemnumberFromBarcode {
     return ($result);
 }
 
+=head3 get_item_authorised_values
+
+  find the types and values for all authorised values assigned to this item.
+
+  parameters:
+    itemnumber
+
+  returns: a hashref malling the authorised value to the value set for this itemnumber
+
+    $authorised_values = {
+             'CCODE'      => undef,
+             'DAMAGED'    => '0',
+             'LOC'        => '3',
+             'LOST'       => '0'
+             'NOT_LOAN'   => '0',
+             'RESTRICTED' => undef,
+             'STACK'      => undef,
+             'WITHDRAWN'  => '0',
+             'branches'   => 'CPL',
+             'cn_source'  => undef,
+             'itemtypes'  => 'SER',
+           };
+
+   Notes: see C4::Biblio::get_biblio_authorised_values for a similar method at the biblio level.
+
+=cut
+
+sub get_item_authorised_values {
+    my $itemnumber = shift;
+
+    # assume that these entries in the authorised_value table are item level.
+    my $query = q(SELECT distinct authorised_value, kohafield
+                    FROM marc_subfield_structure
+                    WHERE kohafield like 'item%'
+                      AND authorised_value != '' );
+
+    my $itemlevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
+    my $iteminfo = GetItem( $itemnumber );
+    # warn( Data::Dumper->Dump( [ $itemlevel_authorised_values ], [ 'itemlevel_authorised_values' ] ) );
+    my $return;
+    foreach my $this_authorised_value ( keys %$itemlevel_authorised_values ) {
+        my $field = $itemlevel_authorised_values->{ $this_authorised_value }->{'kohafield'};
+        $field =~ s/^items\.//;
+        if ( exists $iteminfo->{ $field } ) {
+            $return->{ $this_authorised_value } = $iteminfo->{ $field };
+        }
+    }
+    # warn( Data::Dumper->Dump( [ $return ], [ 'return' ] ) );
+    return $return;
+}
+
+=head3 get_authorised_value_images
+
+  find a list of icons that are appropriate for display based on the
+  authorised values for a biblio.
+
+  parameters: listref of authorised values, such as comes from
+    get_item_authorised_values or
+    from C4::Biblio::get_biblio_authorised_values
+
+  returns: listref of hashrefs for each image. Each hashref looks like
+    this:
+
+      { imageurl => '/intranet-tmpl/prog/img/itemtypeimg/npl/WEB.gif',
+        label    => '',
+        category => '',
+        value    => '', }
+
+  Notes: Currently, I put on the full path to the images on the staff
+  side. This should either be configurable or not done at all. Since I
+  have to deal with 'intranet' or 'opac' in
+  get_biblio_authorised_values, perhaps I should be passing it in.
+
+=cut
+
+sub get_authorised_value_images {
+    my $authorised_values = shift;
+
+    my @imagelist;
+
+    my $authorised_value_list = GetAuthorisedValues();
+    # warn ( Data::Dumper->Dump( [ $authorised_value_list ], [ 'authorised_value_list' ] ) );
+    foreach my $this_authorised_value ( @$authorised_value_list ) {
+        if ( exists $authorised_values->{ $this_authorised_value->{'category'} }
+             && $authorised_values->{ $this_authorised_value->{'category'} } eq $this_authorised_value->{'authorised_value'} ) {
+            # warn ( Data::Dumper->Dump( [ $this_authorised_value ], [ 'this_authorised_value' ] ) );
+            if ( defined $this_authorised_value->{'imageurl'} ) {
+                push @imagelist, { imageurl => C4::Koha::getitemtypeimagelocation( 'intranet', $this_authorised_value->{'imageurl'} ),
+                                   label    => $this_authorised_value->{'lib'},
+                                   category => $this_authorised_value->{'category'},
+                                   value    => $this_authorised_value->{'authorised_value'}, };
+            }
+        }
+    }
+
+    # warn ( Data::Dumper->Dump( [ \@imagelist ], [ 'imagelist' ] ) );
+    return \@imagelist;
+
+}
+
 =head1 LIMITED USE FUNCTIONS
 
 The following functions, while part of the public API,
@@ -1192,9 +1614,22 @@ sub GetMarcItem {
 
     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
     # Also, don't emit a subfield if the underlying field is blank.
-    my $mungeditem = { map {  $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  } keys %{ $itemrecord } };
+    my $mungeditem = { 
+        map {  
+            defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
+        } keys %{ $itemrecord } 
+    };
 
     my $itemmarc = TransformKohaToMarc($mungeditem);
+
+    my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
+    if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
+        my @fields = $itemmarc->fields();
+        if ($#fields > -1) {
+            $fields[0]->add_subfields(@$unlinked_item_subfields);
+        }
+    }
+    
     return $itemmarc;
 
 }
@@ -1433,27 +1868,15 @@ C<items.wthdrawn>
 
 sub _set_defaults_for_add {
     my $item = shift;
-
-    # if dateaccessioned is provided, use it. Otherwise, set to NOW()
-    if (!(exists $item->{'dateaccessioned'}) || 
-         ($item->{'dateaccessioned'} eq '')) {
-        # FIXME add check for invalid date
-        my $today = C4::Dates->new();    
-        $item->{'dateaccessioned'} =  $today->output("iso"); #TODO: check time issues
-    }
-
-    # various item status fields cannot be null
-    $item->{'notforloan'} = 0 unless exists $item->{'notforloan'} and defined $item->{'notforloan'};
-    $item->{'damaged'}    = 0 unless exists $item->{'damaged'}    and defined $item->{'damaged'};
-    $item->{'itemlost'}   = 0 unless exists $item->{'itemlost'}   and defined $item->{'itemlost'};
-    $item->{'wthdrawn'}   = 0 unless exists $item->{'wthdrawn'}   and defined $item->{'wthdrawn'};
+    $item->{dateaccessioned} ||= C4::Dates->new->output('iso');
+    $item->{$_} ||= 0 for (qw( notforloan damaged itemlost wthdrawn));
 }
 
 =head2 _koha_new_item
 
 =over 4
 
-my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
+my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
 
 =back
 
@@ -1462,10 +1885,10 @@ Perform the actual insert into the C<items> table.
 =cut
 
 sub _koha_new_item {
-    my ( $dbh, $item, $barcode ) = @_;
+    my ( $item, $barcode ) = @_;
+    my $dbh=C4::Context->dbh;  
     my $error;
-
-    my $query = 
+    my $query =
            "INSERT INTO items SET
             biblionumber        = ?,
             biblioitemnumber    = ?,
@@ -1498,10 +1921,13 @@ sub _koha_new_item {
             ccode               = ?,
             itype               = ?,
             materials           = ?,
-            uri                 = ?
+            uri = ?,
+            enumchron           = ?,
+            more_subfields_xml  = ?,
+            copynumber          = ?
           ";
     my $sth = $dbh->prepare($query);
-    $sth->execute(
+   $sth->execute(
             $item->{'biblionumber'},
             $item->{'biblioitemnumber'},
             $barcode,
@@ -1532,6 +1958,9 @@ sub _koha_new_item {
             $item->{'itype'},
             $item->{'materials'},
             $item->{'uri'},
+            $item->{'enumchron'},
+            $item->{'more_subfields_xml'},
+            $item->{'copynumber'},
     );
     my $itemnumber = $dbh->{'mysql_insertid'};
     if ( defined $sth->errstr ) {
@@ -1545,7 +1974,7 @@ sub _koha_new_item {
 
 =over 4
 
-my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
+my ($itemnumber,$error) =_koha_modify_item( $item );
 
 =back
 
@@ -1555,7 +1984,8 @@ routine accepts a hashref specifying the columns to update.
 =cut
 
 sub _koha_modify_item {
-    my ( $dbh, $item ) = @_;
+    my ( $item ) = @_;
+    my $dbh=C4::Context->dbh;  
     my $error;
 
     my $query = "UPDATE items SET ";
@@ -1567,9 +1997,9 @@ sub _koha_modify_item {
     $query =~ s/,$//;
     $query .= " WHERE itemnumber=?";
     push @bind, $item->{'itemnumber'};
-    my $sth = $dbh->prepare($query);
+    my $sth = C4::Context->dbh->prepare($query);
     $sth->execute(@bind);
-    if ( $dbh->errstr ) {
+    if ( C4::Context->dbh->errstr ) {
         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
         warn $error;
     }
@@ -1619,7 +2049,7 @@ sub _koha_delete_item {
 
 =over 4
 
-my $item_marc = _marc_from_item_hash($item, $frameworkcode);
+my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
 
 =back
 
@@ -1627,11 +2057,20 @@ Given an item hash representing a complete item record,
 create a C<MARC::Record> object containing an embedded
 tag representing that item.
 
+The third, optional parameter C<$unlinked_item_subfields> is
+an arrayref of subfields (not mapped to C<items> fields per the
+framework) to be added to the MARC representation
+of the item.
+
 =cut
 
 sub _marc_from_item_hash {
     my $item = shift;
     my $frameworkcode = shift;
+    my $unlinked_item_subfields;
+    if (@_) {
+        $unlinked_item_subfields = shift;
+    }
    
     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
     # Also, don't emit a subfield if the underlying field is blank.
@@ -1643,10 +2082,19 @@ sub _marc_from_item_hash {
     foreach my $item_field (keys %{ $mungeditem }) {
         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
-        if (my $field = $item_marc->field($tag)) {
-            $field->add_subfields($subfield => $mungeditem->{$item_field});
-        } else {
-            $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field});
+
+        
+        my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
+        foreach my $value (@values){
+            if (my $field = $item_marc->field($tag)) {
+                $field->add_subfields($subfield => $value);
+            } else {
+                my $add_subfields = [];
+                if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
+                    $add_subfields = $unlinked_item_subfields;
+                }
+                $item_marc->add_fields( $tag, " ", " ", $subfield =>  $value, @$add_subfields);
+            }
         }
     }
 
@@ -1673,7 +2121,6 @@ sub _add_item_field_to_biblio {
     my ($item_marc, $biblionumber, $frameworkcode) = @_;
 
     my $biblio_marc = GetMarcBiblio($biblionumber);
-
     foreach my $field ($item_marc->fields()) {
         $biblio_marc->append_fields($field);
     }
@@ -1699,7 +2146,7 @@ replace it with the tag from C<$item_marc>.
 sub _replace_item_field_in_biblio {
     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
     my $dbh = C4::Context->dbh;
-    
+
     # get complete MARC record & replace the item field by the new one
     my $completeRecord = GetMarcBiblio($biblionumber);
     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
@@ -1724,4 +2171,115 @@ sub _replace_item_field_in_biblio {
     ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
 }
 
+=head2 _repack_item_errors
+
+Add an error message hash generated by C<CheckItemPreSave>
+to a list of errors.
+
+=cut
+
+sub _repack_item_errors {
+    my $item_sequence_num = shift;
+    my $item_ref = shift;
+    my $error_ref = shift;
+
+    my @repacked_errors = ();
+
+    foreach my $error_code (sort keys %{ $error_ref }) {
+        my $repacked_error = {};
+        $repacked_error->{'item_sequence'} = $item_sequence_num;
+        $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
+        $repacked_error->{'error_code'} = $error_code;
+        $repacked_error->{'error_information'} = $error_ref->{$error_code};
+        push @repacked_errors, $repacked_error;
+    } 
+
+    return @repacked_errors;
+}
+
+=head2 _get_unlinked_item_subfields
+
+=over 4
+
+my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
+
+=back
+
+=cut
+
+sub _get_unlinked_item_subfields {
+    my $original_item_marc = shift;
+    my $frameworkcode = shift;
+
+    my $marcstructure = GetMarcStructure(1, $frameworkcode);
+
+    # assume that this record has only one field, and that that
+    # field contains only the item information
+    my $subfields = [];
+    my @fields = $original_item_marc->fields();
+    if ($#fields > -1) {
+        my $field = $fields[0];
+           my $tag = $field->tag();
+        foreach my $subfield ($field->subfields()) {
+            if (defined $subfield->[1] and
+                $subfield->[1] ne '' and
+                !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
+                push @$subfields, $subfield->[0] => $subfield->[1];
+            }
+        }
+    }
+    return $subfields;
+}
+
+=head2 _get_unlinked_subfields_xml
+
+=over 4
+
+my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
+
+=back
+
+=cut
+
+sub _get_unlinked_subfields_xml {
+    my $unlinked_item_subfields = shift;
+
+    my $xml;
+    if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
+        my $marc = MARC::Record->new();
+        # use of tag 999 is arbitrary, and doesn't need to match the item tag
+        # used in the framework
+        $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
+        $marc->encoding("UTF-8");    
+        $xml = $marc->as_xml("USMARC");
+    }
+
+    return $xml;
+}
+
+=head2 _parse_unlinked_item_subfields_from_xml
+
+=over 4
+
+my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
+
+=back
+
+=cut
+
+sub  _parse_unlinked_item_subfields_from_xml {
+    my $xml = shift;
+
+    return unless defined $xml and $xml ne "";
+    my $marc = MARC::Record->new_from_xml(StripNonXmlChars($xml),'UTF-8');
+    my $unlinked_subfields = [];
+    my @fields = $marc->fields();
+    if ($#fields > -1) {
+        foreach my $subfield ($fields[0]->subfields()) {
+            push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
+        }
+    }
+    return $unlinked_subfields;
+}
+
 1;