Bug 19919: Stop using paidfor altogether
[koha-equinox.git] / C4 / Items.pm
index ec2c0a4..e8251d2 100644 (file)
@@ -27,7 +27,6 @@ BEGIN {
     @ISA = qw(Exporter);
 
     @EXPORT = qw(
-        GetItem
         AddItemFromMarc
         AddItem
         AddItemBatchFromMarc
@@ -48,7 +47,6 @@ BEGIN {
         DelItemCheck
         MoveItemFromBiblio
         CartToShelf
-        ShelfToCart
         GetAnalyticsCount
         SearchItemsByField
         SearchItems
@@ -57,6 +55,7 @@ BEGIN {
 }
 
 use Carp;
+use Try::Tiny;
 use C4::Context;
 use C4::Koha;
 use C4::Biblio;
@@ -100,12 +99,6 @@ indexed (e.g., by Zebra), but means that each item
 modification transaction must keep the items table
 and the MARC XML in sync at all times.
 
-Consequently, all code that creates, modifies, or deletes
-item records B<must> use an appropriate function from 
-C<C4::Items>.  If no existing function is suitable, it is
-better to add one to C<C4::Items> than to use add
-one-off SQL statements to add or modify items.
-
 The items table will be considered authoritative.  In other
 words, if there is ever a discrepancy between the items
 table and the MARC XML, the items table should be considered
@@ -123,41 +116,6 @@ of C<C4::Items>
 
 =cut
 
-=head2 GetItem
-
-  $item = GetItem($itemnumber,$barcode,$serial);
-
-Return item information, for a given itemnumber or barcode.
-The return value is a hashref mapping item column
-names to values.  If C<$serial> is true, include serial publication data.
-
-=cut
-
-sub GetItem {
-    my ($itemnumber,$barcode, $serial) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $item;
-    if ($itemnumber) {
-        $item = Koha::Items->find( $itemnumber );
-    } else {
-        $item = Koha::Items->find( { barcode => $barcode } );
-    }
-
-    return unless ( $item );
-
-    my $data = $item->unblessed();
-    $data->{itype} = $item->effective_itemtype(); # set the correct itype
-
-    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();
-    }
-
-    return $data;
-}    # sub GetItem
-
 =head2 CartToShelf
 
   CartToShelf($itemnumber);
@@ -177,32 +135,10 @@ sub CartToShelf {
         croak "FAILED CartToShelf() - no itemnumber supplied";
     }
 
-    my $item = GetItem($itemnumber);
-    if ( $item->{location} eq 'CART' ) {
-        $item->{location} = $item->{permanent_location};
-        ModItem($item, undef, $itemnumber);
-    }
-}
-
-=head2 ShelfToCart
-
-  ShelfToCart($itemnumber);
-
-Set the current shelving location of the item
-to shelving cart ('CART').
-
-=cut
-
-sub ShelfToCart {
-    my ( $itemnumber ) = @_;
-
-    unless ( $itemnumber ) {
-        croak "FAILED ShelfToCart() - no itemnumber supplied";
+    my $item = Koha::Items->find($itemnumber);
+    if ( $item->location eq 'CART' ) {
+        ModItem({ location => $item->permanent_location}, undef, $itemnumber);
     }
-
-    my $item = GetItem($itemnumber);
-    $item->{'location'} = 'CART';
-    ModItem($item, undef, $itemnumber);
 }
 
 =head2 AddItemFromMarc
@@ -220,14 +156,14 @@ sub AddItemFromMarc {
     my $dbh = C4::Context->dbh;
 
     # parse item hash from MARC
-    my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
-    my ($itemtag,$itemsubfield)=C4::Biblio::GetMarcFromKohaField("items.itemnumber",$frameworkcode);
-
-    my $localitemmarc=MARC::Record->new;
-    $localitemmarc->append_fields($source_item_marc->field($itemtag));
-    my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
-    my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
-    return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
+    my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
+    my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
+
+    my $localitemmarc = MARC::Record->new;
+    $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
+    my $item = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
+    my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
+    return AddItem( $item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields );
 }
 
 =head2 AddItem
@@ -284,11 +220,13 @@ sub AddItem {
 
     $item->{'itemnumber'} = $itemnumber;
 
-    ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
+    C4::Biblio::ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
 
     logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
       if C4::Context->preference("CataloguingLog");
 
+    _after_item_action_hooks({ action => 'create', item_id => $itemnumber });
+
     return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
 }
 
@@ -455,7 +393,6 @@ sub _build_default_values_for_mod_marc {
         materials                => undef,
         new_status               => undef,
         notforloan               => 0,
-        # paidfor => undef, # commented, see bug 12817
         price                    => undef,
         replacementprice         => undef,
         replacementpricedate     => undef,
@@ -511,8 +448,7 @@ ModItem(
     }
 );
 
-Change one or more columns in an item record and update
-the MARC representation of the item.
+Change one or more columns in an item record.
 
 The first argument is a hashref mapping from item column
 names to the new values.  The second and third arguments
@@ -556,12 +492,12 @@ sub ModItem {
 
     my @fields = qw( itemlost withdrawn damaged );
 
-    # Only call GetItem if we need to set an "on" date field
+    # Only retrieve the item if we need to set an "on" date field
     if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
-        my $pre_mod_item = GetItem( $item->{'itemnumber'} );
+        my $pre_mod_item = Koha::Items->find( $item->{'itemnumber'} );
         for my $field (@fields) {
             if (    defined( $item->{$field} )
-                and not $pre_mod_item->{$field}
+                and not $pre_mod_item->$field
                 and $item->{$field} )
             {
                 $item->{ $field . '_on' } =
@@ -595,6 +531,8 @@ sub ModItem {
     # item status is possible
     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
 
+    _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
+
     logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
       if $log_action && C4::Context->preference("CataloguingLog");
 }
@@ -612,9 +550,10 @@ sub ModItemTransfer {
     my ( $itemnumber, $frombranch, $tobranch ) = @_;
 
     my $dbh = C4::Context->dbh;
+    my $item = Koha::Items->find( $itemnumber );
 
     # Remove the 'shelving cart' location status if it is being used.
-    CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
+    CartToShelf( $itemnumber ) if ( $item->location eq 'CART' && $item->permanent_location ne 'CART' );
 
     $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
 
@@ -624,7 +563,7 @@ sub ModItemTransfer {
         VALUES (?, ?, NOW(), ?)");
     $sth->execute($itemnumber, $frombranch, $tobranch);
 
-    ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
+    ModItem({ holdingbranch => $tobranch }, undef, $itemnumber, { log_action => 0 });
     ModDateLastSeen($itemnumber);
     return;
 }
@@ -678,6 +617,8 @@ sub DelItem {
 
     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
 
+    _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
+
     #search item field code
     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
     return $deleted;
@@ -804,6 +745,7 @@ sub GetItemsForInventory {
     my ( $parameters ) = @_;
     my $minlocation  = $parameters->{'minlocation'}  // '';
     my $maxlocation  = $parameters->{'maxlocation'}  // '';
+    my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
     my $location     = $parameters->{'location'}     // '';
     my $itemtype     = $parameters->{'itemtype'}     // '';
     my $ignoreissued = $parameters->{'ignoreissued'} // '';
@@ -813,14 +755,18 @@ sub GetItemsForInventory {
     my $offset       = $parameters->{'offset'}       // '';
     my $size         = $parameters->{'size'}         // '';
     my $statushash   = $parameters->{'statushash'}   // '';
+    my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
 
     my $dbh = C4::Context->dbh;
     my ( @bind_params, @where_strings );
 
+    my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
+    my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
+
     my $select_columns = q{
-        SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
+        SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
     };
-    my $select_count = q{SELECT COUNT(*)};
+    my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
     my $query = q{
         FROM items
         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
@@ -836,13 +782,13 @@ sub GetItemsForInventory {
     }
 
     if ($minlocation) {
-        push @where_strings, 'itemcallnumber >= ?';
-        push @bind_params, $minlocation;
+        push @where_strings, 'items.cn_sort >= ?';
+        push @bind_params, $min_cnsort;
     }
 
     if ($maxlocation) {
-        push @where_strings, 'itemcallnumber <= ?';
-        push @bind_params, $maxlocation;
+        push @where_strings, 'items.cn_sort <= ?';
+        push @bind_params, $max_cnsort;
     }
 
     if ($datelastseen) {
@@ -875,6 +821,11 @@ sub GetItemsForInventory {
         push @where_strings, 'issues.date_due IS NULL';
     }
 
+    if ( $ignore_waiting_holds ) {
+        $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
+        push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
+    }
+
     if ( @where_strings ) {
         $query .= 'WHERE ';
         $query .= join ' AND ', @where_strings;
@@ -999,6 +950,7 @@ sub GetItemsInfo {
            holding.opac_info as holding_branch_opac_info,
            home.opac_info as home_branch_opac_info
     ";
+    $query .= ",IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold" if !C4::Context->preference('AllowItemsOnHoldCheckout');
     $query .= "
      FROM items
      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
@@ -1011,6 +963,8 @@ sub GetItemsInfo {
      LEFT JOIN serial USING (serialid)
      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
+    $query .= "
+    LEFT JOIN tmp_holdsqueue USING (itemnumber)" if !C4::Context->preference('AllowItemsOnHoldCheckout');
     $query .= q|
     LEFT JOIN localization ON itemtypes.itemtype = localization.code
         AND localization.entity = 'itemtypes'
@@ -1041,8 +995,8 @@ sub GetItemsInfo {
 
         # get restricted status and description if applicable
         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
-        $data->{restricted}     = $descriptions->{lib} // '';
-        $data->{restrictedopac} = $descriptions->{opac_description} // '';
+        $data->{restrictedvalue}     = $descriptions->{lib} // '';
+        $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
 
         # my stack procedures
         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
@@ -1190,8 +1144,12 @@ references on array of itemnumbers.
 
 sub get_hostitemnumbers_of {
     my ($biblionumber) = @_;
-    my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
 
+    if( !C4::Context->preference('EasyAnalyticalRecords') ) {
+        return ();
+    }
+
+    my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
     return unless $marcrecord;
 
     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
@@ -1325,13 +1283,12 @@ sub GetMarcItem {
     # while the other treats the MARC representation as authoritative
     # under certain circumstances.
 
-    my $itemrecord = GetItem($itemnumber);
+    my $item = Koha::Items->find($itemnumber) or return;
 
     # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
     # Also, don't emit a subfield if the underlying field is blank.
 
-    
-    return Item2Marc($itemrecord,$biblionumber);
+    return Item2Marc($item->unblessed, $biblionumber);
 
 }
 sub Item2Marc {
@@ -1342,9 +1299,7 @@ sub Item2Marc {
         } keys %{ $itemrecord } 
     };
     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
-    my $itemmarc = C4::Biblio::TransformKohaToMarc(
-        $mungeditem, { no_split => 1},
-    );
+    my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
         "items.itemnumber", $framework,
     );
@@ -1620,7 +1575,6 @@ sub _koha_new_item {
             itemnotes           = ?,
             itemnotes_nonpublic = ?,
             holdingbranch       = ?,
-            paidfor             = ?,
             location            = ?,
             permanent_location  = ?,
             onloan              = ?,
@@ -1664,7 +1618,6 @@ sub _koha_new_item {
             $item->{'itemnotes'},
             $item->{'itemnotes_nonpublic'},
             $item->{'holdingbranch'},
-            $item->{'paidfor'},
             $item->{'location'},
             $item->{'permanent_location'},
             $item->{'onloan'},
@@ -1772,31 +1725,21 @@ sub ItemSafeToDelete {
 
     my $countanalytics = GetAnalyticsCount($itemnumber);
 
-    # check that there is no issue on this item before deletion.
-    my $sth = $dbh->prepare(
-        q{
-        SELECT COUNT(*) FROM issues
-        WHERE itemnumber = ?
-    }
-    );
-    $sth->execute($itemnumber);
-    my ($onloan) = $sth->fetchrow;
-
-    my $item = GetItem($itemnumber);
+    my $item = Koha::Items->find($itemnumber) or return;
 
-    if ($onloan) {
+    if ($item->checkout) {
         $status = "book_on_loan";
     }
     elsif ( defined C4::Context->userenv
         and !C4::Context->IsSuperLibrarian()
         and C4::Context->preference("IndependentBranches")
-        and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
+        and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
     {
         $status = "not_same_branch";
     }
     else {
         # check it doesn't have a waiting reserve
-        $sth = $dbh->prepare(
+        my $sth = $dbh->prepare(
             q{
             SELECT COUNT(*) FROM reserves
             WHERE (found = 'W' OR found = 'T')
@@ -2028,7 +1971,7 @@ sub _get_unlinked_item_subfields {
     my $original_item_marc = shift;
     my $frameworkcode = shift;
 
-    my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
+    my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
 
     # assume that this record has only one field, and that that
     # field contains only the item information
@@ -2308,7 +2251,7 @@ sub SearchItems {
         $query .= qq{ AND $where_str };
     }
 
-    $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? };
+    $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
     push @where_args, C4::Context->preference('marcflavour');
 
     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
@@ -2457,6 +2400,8 @@ sub PrepareItemrecordDisplay {
                     $defaultvalue =~ s/"/&quot;/g;
                 }
 
+                my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
+
                 # search for itemcallnumber if applicable
                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
                     && C4::Context->preference('itemcallnumber') ) {
@@ -2594,17 +2539,17 @@ sub PrepareItemrecordDisplay {
                         my $tab= $plugin->noclick? '-1': '';
                         my $class= $plugin->noclick? ' disabled': '';
                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
-                        $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
+                        $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
                     } else {
                         warn $plugin->errstr;
-                        $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />); # supply default input form
+                        $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
                     }
                 }
                 elsif ( $tag eq '' ) {       # it's an hidden field
-                    $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" />);
+                    $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
                 }
                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
-                    $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" />);
+                    $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
                 }
                 elsif ( length($defaultvalue) > 100
                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
@@ -2613,9 +2558,9 @@ sub PrepareItemrecordDisplay {
                                   500 <= $tag && $tag < 600                     )
                           ) {
                     # oversize field (textarea)
-                    $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");
+                    $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
                 } else {
-                    $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
+                    $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"$maxlength\" />";
                 }
                 push( @loop_data, \%subfield_data );
             }
@@ -2647,10 +2592,13 @@ sub ToggleNewStatus {
         my $age = $rule->{age};
         my $conditions = $rule->{conditions};
         my $substitutions = $rule->{substitutions};
+        foreach ( @$substitutions ) {
+            ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
+        }
         my @params;
 
         my $query = q|
-            SELECT items.biblionumber, items.itemnumber
+            SELECT items.*
             FROM items
             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
             WHERE 1
@@ -2681,10 +2629,10 @@ sub ToggleNewStatus {
         while ( my $values = $sth->fetchrow_hashref ) {
             my $biblionumber = $values->{biblionumber};
             my $itemnumber = $values->{itemnumber};
-            my $item = C4::Items::GetItem( $itemnumber );
             for my $substitution ( @$substitutions ) {
                 next unless $substitution->{field};
-                C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
+                next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
+                C4::Items::ModItem( { $substitution->{item_field} => $substitution->{value} }, $biblionumber, $itemnumber )
                     unless $report_only;
                 push @{ $report->{$itemnumber} }, $substitution;
             }
@@ -2694,5 +2642,38 @@ sub ToggleNewStatus {
     return $report;
 }
 
+=head2 _after_item_action_hooks
+
+Helper method that takes care of calling all plugin hooks
+
+=cut
+
+sub _after_item_action_hooks {
+    my ( $args ) = @_;
+
+    my $item_id = $args->{item_id};
+    my $action  = $args->{action};
+
+    if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
+
+        my @plugins = Koha::Plugins->new->GetPlugins({
+            method => 'after_item_action',
+        });
+
+        if (@plugins) {
+
+            my $item = Koha::Items->find( $item_id );
+
+            foreach my $plugin ( @plugins ) {
+                try {
+                    $plugin->after_item_action({ action => $action, item => $item, item_id => $item_id });
+                }
+                catch {
+                    warn "$_";
+                };
+            }
+        }
+    }
+}
 
 1;