X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=tools%2FbatchMod.pl;h=0988571b5d40fe46fa36954439861fbf1cd0c317;hb=b52c7d7b3664b81b41bade88cbb3491dcdb81693;hp=0ef4248bb9d2c9980e270cbe51205a2ce00d7b14;hpb=c02b33e4254c72d20941f87fe7a5c58bcb5838f9;p=srvgit diff --git a/tools/batchMod.pl b/tools/batchMod.pl index 0ef4248bb9..0988571b5d 100755 --- a/tools/batchMod.pl +++ b/tools/batchMod.pl @@ -19,25 +19,40 @@ # along with Koha; if not, see . use CGI qw ( -utf8 ); -use strict; -#use warnings; FIXME - Bug 2505 -use C4::Auth; -use C4::Output; -use C4::Biblio; -use C4::Items; -use C4::Circulation; +use Modern::Perl; +use Try::Tiny qw( catch try ); + +use C4::Auth qw( get_template_and_user haspermission ); +use C4::Output qw( output_html_with_http_headers ); +use C4::Biblio qw( + DelBiblio + GetAuthorisedValueDesc + GetMarcFromKohaField + GetMarcStructure + IsMarcStructureInternal + TransformHtmlToXml +); +use C4::Items qw( GetItemsInfo Item2Marc ModItemFromMarc ); +use C4::Circulation qw( barcodedecode LostItem IsItemIssued ); use C4::Context; use C4::Koha; -use C4::Branch; use C4::BackgroundJob; -use C4::ClassSource; -use C4::Debug; -use C4::Members; +use C4::ClassSource qw( GetClassSources GetClassSource ); use MARC::File::XML; -use List::MoreUtils qw/uniq/; -use Koha::DateUtils; - -my $input = new CGI; +use List::MoreUtils qw( uniq ); + +use Koha::Database; +use Koha::Exceptions::Exception; +use Koha::AuthorisedValues; +use Koha::Biblios; +use Koha::DateUtils qw( dt_from_string ); +use Koha::Items; +use Koha::ItemTypes; +use Koha::Patrons; +use Koha::SearchEngine::Indexer; +use Koha::UI::Form::Builder::Item; + +my $input = CGI->new; my $dbh = C4::Context->dbh; my $error = $input->param('error'); my @itemnumbers = $input->multi_param('itemnumber'); @@ -45,10 +60,9 @@ my $biblionumber = $input->param('biblionumber'); my $op = $input->param('op'); my $del = $input->param('del'); my $del_records = $input->param('del_records'); -my $completedJobID = $input->param('completedJobID'); -my $runinbackground = $input->param('runinbackground'); my $src = $input->param('src'); my $use_default_values = $input->param('use_default_values'); +my $exclude_from_local_holds_priority = $input->param('exclude_from_local_holds_priority'); my $template_name; my $template_flag; @@ -61,24 +75,23 @@ if (!defined $op) { $template_flag = ($del) ? { tools => 'items_batchdel' } : { tools => 'items_batchmod' }; } - my ($template, $loggedinuser, $cookie) = get_template_and_user({template_name => $template_name, query => $input, type => "intranet", - authnotrequired => 0, flagsrequired => $template_flag, }); +$template->param( searchid => scalar $input->param('searchid'), ); + # Does the user have a restricted item edition permission? -my $uid = $loggedinuser ? GetMember( borrowernumber => $loggedinuser )->{userid} : undef; +my $uid = $loggedinuser ? Koha::Patrons->find( $loggedinuser )->userid : undef; my $restrictededition = $uid ? haspermission($uid, {'tools' => 'items_batchmod_restricted'}) : undef; # In case user is a superlibrarian, edition is not restricted $restrictededition = 0 if ($restrictededition != 0 && C4::Context->IsSuperLibrarian()); $template->param(del => $del); -my $itemrecord; my $nextop=""; my @errors; # store errors found while checking data BEFORE saving item. my $items_display_hashref; @@ -101,118 +114,228 @@ if ($op eq "action") { my @tags = $input->multi_param('tag'); my @subfields = $input->multi_param('subfield'); my @values = $input->multi_param('field_value'); - my @disabled = $input->multi_param('disable_input'); - # build indicator hash. - my @ind_tag = $input->multi_param('ind_tag'); - my @indicator = $input->multi_param('indicator'); + my @searches = $input->multi_param('regex_search'); + my @replaces = $input->multi_param('regex_replace'); + my @modifiers = $input->multi_param('regex_modifiers'); + + my $upd_biblionumbers; + my $del_biblionumbers; + if ( $del ) { + try { + my $schema = Koha::Database->new->schema; + $schema->txn_do( + sub { + foreach my $itemnumber (@itemnumbers) { + my $item = Koha::Items->find($itemnumber); + next + unless $item + ; # Should have been tested earlier, but just in case... + my $itemdata = $item->unblessed; + my $return = $item->safe_delete; + if ( ref( $return ) ) { + $deleted_items++; + push @$upd_biblionumbers, $itemdata->{'biblionumber'}; + } + else { + $not_deleted_items++; + push @not_deleted, + { + biblionumber => $itemdata->{'biblionumber'}, + itemnumber => $itemdata->{'itemnumber'}, + barcode => $itemdata->{'barcode'}, + title => $itemdata->{'title'}, + reason => $return, + }; + } + + # If there are no items left, delete the biblio + if ($del_records) { + my $itemscount = Koha::Biblios->find( $itemdata->{'biblionumber'} )->items->count; + if ( $itemscount == 0 ) { + my $error = DelBiblio( $itemdata->{'biblionumber'}, { skip_record_index => 1 } ); + unless ($error) { + $deleted_records++; + push @$del_biblionumbers, $itemdata->{'biblionumber'}; + if ( $src eq 'CATALOGUING' ) { + # We are coming catalogue/detail.pl, there were items from a single bib record + $template->param( biblio_deleted => 1 ); + } + } + } + } + } + if (@not_deleted) { + Koha::Exceptions::Exception->throw( + 'Some items have not been deleted, rolling back'); + } + } + ); + } + catch { + warn $_; + if ( $_->isa('Koha::Exceptions::Exception') ) { + $template->param( deletion_failed => 1 ); + } + die "Something terrible has happened!" + if ($_ =~ /Rollback failed/); # Rollback failed + }; + } - # Is there something to modify ? - # TODO : We shall use this var to warn the user in case no modification was done to the items - my $values_to_modify = scalar(grep {!/^$/} @values); - my $values_to_blank = scalar(@disabled); - my $marcitem; + else { # modification - # Once the job is done - if ($completedJobID) { - # If we have a reasonable amount of items, we display them - if (scalar(@itemnumbers) <= ( C4::Context->preference("MaxItemsToDisplayForBatchDel") // 1000 ) ) { - $items_display_hashref=BuildItemsData(@itemnumbers); - } else { - # Else, we only display the barcode - my @simple_items_display = map {{ itemnumber => $_, barcode => (GetBarcodeFromItemnumber($_) or ""), biblionumber => (GetBiblionumberFromItemnumber($_) or "") }} @itemnumbers; - $template->param("simple_items_display" => \@simple_items_display); - } - - # Setting the job as done - my $job = C4::BackgroundJob->fetch($sessionID, $completedJobID); - - # Calling the template - add_saved_job_results_to_template($template, $completedJobID); + my @columns = Koha::Items->columns; - } else { - # While the job is getting done - - # Job size is the number of items we have to process - my $job_size = scalar(@itemnumbers); - my $job = undef; - - # If we asked for background processing - if ($runinbackground) { - $job = put_in_background($job_size); - } - - #initializing values for updates - my ( $itemtagfield, $itemtagsubfield) = &GetMarcFromKohaField("items.itemnumber", ""); - if ($values_to_modify){ - my $xml = TransformHtmlToXml(\@tags,\@subfields,\@values,\@indicator,\@ind_tag, 'ITEM'); - $marcitem = MARC::Record::new_from_xml($xml, 'UTF-8'); - } - if ($values_to_blank){ - foreach my $disabledsubf (@disabled){ - if ($marcitem && $marcitem->field($itemtagfield)){ - $marcitem->field($itemtagfield)->update( $disabledsubf => "" ); - } - else { - $marcitem = MARC::Record->new(); - $marcitem->append_fields( MARC::Field->new( $itemtagfield, '', '', $disabledsubf => "" ) ); - } - } + my $new_item_data; + my @columns_with_regex; + for my $c ( @columns ) { + if ( $c eq 'more_subfields_xml' ) { + my @more_subfields_xml = $input->multi_param("items.more_subfields_xml"); + my @unlinked_item_subfields; + for my $subfield ( @more_subfields_xml ) { + my $v = $input->param('items.more_subfields_xml_' . $subfield); + push @unlinked_item_subfields, $subfield, $v; + } + if ( @unlinked_item_subfields ) { + 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"); + # FIXME This is WRONG! We need to use the values that haven't been modified by the batch tool! + $new_item_data->{more_subfields_xml} = $marc->as_xml("USMARC"); + next; + } + $new_item_data->{more_subfields_xml} = undef; + # FIXME deal with more_subfields_xml and @subfields_to_blank + } elsif ( grep { $c eq $_ } @subfields_to_blank ) { + # Empty this column + $new_item_data->{$c} = undef + } else { + + my @v = grep { $_ ne "" } + uniq $input->multi_param( "items." . $c ); + + next unless @v; + + $new_item_data->{$c} = join ' | ', @v; + } + + if ( my $regex_search = $input->param('items.'.$c.'_regex_search') ) { + push @columns_with_regex, $c; + } } - # For each item - my $i = 1; - foreach my $itemnumber(@itemnumbers){ - - $job->progress($i) if $runinbackground; - my $itemdata = GetItem($itemnumber); - if ( $del ){ - my $return = DelItemCheck( $itemdata->{'biblionumber'}, $itemdata->{'itemnumber'}); - if ($return == 1) { - $deleted_items++; - } else { - $not_deleted_items++; - push @not_deleted, - { biblionumber => $itemdata->{'biblionumber'}, - itemnumber => $itemdata->{'itemnumber'}, - barcode => $itemdata->{'barcode'}, - title => $itemdata->{'title'}, - $return => 1 - }; - } + try { + my $schema = Koha::Database->new->schema; + $schema->txn_do( + sub { + + foreach my $itemnumber (@itemnumbers) { + my $item = Koha::Items->find($itemnumber); + next + unless $item + ; # Should have been tested earlier, but just in case... + my $itemdata = $item->unblessed; + + my $modified_holds_priority = 0; + if ( defined $exclude_from_local_holds_priority && $exclude_from_local_holds_priority ne "" ) { + if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) { + $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store; + $modified_holds_priority = 1; + } + } - # If there are no items left, delete the biblio - if ( $del_records ) { - my $itemscount = GetItemsCount($itemdata->{'biblionumber'}); - if ( $itemscount == 0 ) { - my $error = DelBiblio($itemdata->{'biblionumber'}); - $deleted_records++ unless ( $error ); + my $modified = 0; + for my $c ( @columns_with_regex ) { + my $regex_search = $input->param('items.'.$c.'_regex_search'); + my $old_value = $item->$c; + + my $value = apply_regex( + { + search => $regex_search, + replace => $input->param( + 'items' . $c . '_regex_replace' + ), + modifiers => $input->param( + 'items' . $c . '_regex_modifiers' + ), + value => $old_value, + } + ); + unless ( $old_value eq $value ) { + $modified++; + $item->$c($value); } } - } else { - if ($values_to_modify || $values_to_blank) { - my $localmarcitem = Item2Marc($itemdata); - - my $modified = UpdateMarcWith( $marcitem, $localmarcitem ); - if ( $modified ) { - eval { - if ( my $item = ModItemFromMarc( $localmarcitem, $itemdata->{biblionumber}, $itemnumber ) ) { - LostItem($itemnumber, 'MARK RETURNED') if $item->{itemlost} and not $itemdata->{itemlost}; + + $modified += scalar(keys %$new_item_data); # FIXME This is incorrect if old value == new value. Should we loop of the keys and compare the before/after values? + if ( $modified) { + my $itemlost_pre = $item->itemlost; + $item->set($new_item_data)->store({skip_record_index => 1}); + + push @$upd_biblionumbers, $itemdata->{'biblionumber'}; + + LostItem( + $item->itemnumber, 'batchmod', undef, + { skip_record_index => 1 } + ) if $item->itemlost + and not $itemlost_pre; } - }; - } - if ( $runinbackground ) { - $modified_items++ if $modified; - $modified_fields += $modified; - $job->set({ - modified_items => $modified_items, - modified_fields => $modified_fields, - }); + + $modified_items++ if $modified || $modified_holds_priority; + $modified_fields += $modified + $modified_holds_priority; + } } - } - } - $i++; - } + ); + } + catch { + warn $_; + die "Something terrible has happened!" + if ($_ =~ /Rollback failed/); # Rollback failed + }; } + + $upd_biblionumbers = [ uniq @$upd_biblionumbers ]; # Only update each bib once + + # Don't send specialUpdate for records we are going to delete + my %del_bib_hash = map{ $_ => undef } @$del_biblionumbers; + @$upd_biblionumbers = grep( ! exists( $del_bib_hash{$_} ), @$upd_biblionumbers ); + + my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX }); + $indexer->index_records( $upd_biblionumbers, 'specialUpdate', "biblioserver", undef ) if @$upd_biblionumbers; + $indexer->index_records( $del_biblionumbers, 'recordDelete', "biblioserver", undef ) if @$del_biblionumbers; + + # Once the job is done + # If we have a reasonable amount of items, we display them + my $max_items = $del ? C4::Context->preference("MaxItemsToDisplayForBatchDel") : C4::Context->preference("MaxItemsToDisplayForBatchMod"); + if (scalar(@itemnumbers) <= $max_items ){ + if (scalar(@itemnumbers) <= 1000 ) { + $items_display_hashref=BuildItemsData(@itemnumbers); + } else { + # Else, we only display the barcode + my @simple_items_display = map { + my $itemnumber = $_; + my $item = Koha::Items->find($itemnumber); + { + itemnumber => $itemnumber, + barcode => $item ? ( $item->barcode // q{} ) : q{}, + biblionumber => $item ? $item->biblio->biblionumber : q{}, + }; + } @itemnumbers; + $template->param("simple_items_display" => \@simple_items_display); + } + } else { + $template->param( "too_many_items_display" => scalar(@itemnumbers) ); + $template->param( "job_completed" => 1 ); + } + + + # Calling the template + $template->param( + modified_items => $modified_items, + modified_fields => $modified_fields, + ); + } # #------------------------------------------------------------------------------- @@ -222,273 +345,95 @@ if ($op eq "action") { if ($op eq "show"){ my $filefh = $input->upload('uploadfile'); my $filecontent = $input->param('filecontent'); - my @notfoundbarcodes; + my ( @notfoundbarcodes, @notfounditemnumbers); - my @contentlist; + my $split_chars = C4::Context->preference('BarcodeSeparators'); if ($filefh){ + binmode $filefh, ':encoding(UTF-8)'; + my @contentlist; while (my $content=<$filefh>){ $content =~ s/[\r\n]*$//; push @contentlist, $content if $content; } if ($filecontent eq 'barcode_file') { - foreach my $barcode (@contentlist) { - - my $itemnumber = GetItemnumberFromBarcode($barcode); - if ($itemnumber) { - push @itemnumbers,$itemnumber; - } else { - push @notfoundbarcodes, $barcode; - } - } + @contentlist = grep /\S/, ( map { split /[$split_chars]/ } @contentlist ); + @contentlist = uniq @contentlist; + # Note: adding lc for case insensitivity + my %itemdata = map { lc($_->{barcode}) => $_->{itemnumber} } @{ Koha::Items->search({ barcode => \@contentlist }, { columns => [ 'itemnumber', 'barcode' ] } )->unblessed }; + @itemnumbers = map { exists $itemdata{lc $_} ? $itemdata{lc $_} : () } @contentlist; + @notfoundbarcodes = grep { !exists $itemdata{lc $_} } @contentlist; } elsif ( $filecontent eq 'itemid_file') { - @itemnumbers = @contentlist; + @contentlist = uniq @contentlist; + my %itemdata = map { $_->{itemnumber} => 1 } @{ Koha::Items->search({ itemnumber => \@contentlist }, { columns => [ 'itemnumber' ] } )->unblessed }; + @itemnumbers = grep { exists $itemdata{$_} } @contentlist; + @notfounditemnumbers = grep { !exists $itemdata{$_} } @contentlist; } } else { - if (defined $biblionumber){ + if (defined $biblionumber && !@itemnumbers){ my @all_items = GetItemsInfo( $biblionumber ); foreach my $itm (@all_items) { push @itemnumbers, $itm->{itemnumber}; } } - if ( my $list=$input->param('barcodelist')){ - push my @barcodelist, uniq( split(/\s\n/, $list) ); + if ( my $list = $input->param('barcodelist') ) { + my @barcodelist = grep /\S/, ( split /[$split_chars]/, $list ); + @barcodelist = uniq @barcodelist; - foreach my $barcode (@barcodelist) { - - my $itemnumber = GetItemnumberFromBarcode($barcode); - if ($itemnumber) { - push @itemnumbers,$itemnumber; - } else { - push @notfoundbarcodes, $barcode; - } - } + @barcodelist = map { barcodedecode( $_ ) } @barcodelist; + # Note: adding lc for case insensitivity + my %itemdata = map { lc($_->{barcode}) => $_->{itemnumber} } @{ Koha::Items->search({ barcode => \@barcodelist }, { columns => [ 'itemnumber', 'barcode' ] } )->unblessed }; + @itemnumbers = map { exists $itemdata{lc $_} ? $itemdata{lc $_} : () } @barcodelist; + @notfoundbarcodes = grep { !exists $itemdata{lc $_} } @barcodelist; } } # Flag to tell the template there are valid results, hidden or not if(scalar(@itemnumbers) > 0){ $template->param("itemresults" => 1); } # Only display the items if there are no more than pref MaxItemsToProcessForBatchMod or MaxItemsToDisplayForBatchDel - my $max_items = $del + my $max_display_items = $del ? C4::Context->preference("MaxItemsToDisplayForBatchDel") - : C4::Context->preference("MaxItemsToProcessForBatchMod"); - if (scalar(@itemnumbers) <= ( $max_items // 1000 ) ) { + : C4::Context->preference("MaxItemsToDisplayForBatchMod"); + $template->param("too_many_items_process" => scalar(@itemnumbers)) if !$del && scalar(@itemnumbers) > C4::Context->preference("MaxItemsToProcessForBatchMod"); + if (scalar(@itemnumbers) <= ( $max_display_items // 1000 ) ) { $items_display_hashref=BuildItemsData(@itemnumbers); } else { - $template->param("too_many_items" => scalar(@itemnumbers)); + $template->param("too_many_items_display" => scalar(@itemnumbers)); # Even if we do not display the items, we need the itemnumbers $template->param(itemnumbers_array => \@itemnumbers); } -# now, build the item form for entering a new item -my @loop_data =(); -my $i=0; -my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : ""; -my $query = qq{SELECT authorised_value, lib FROM authorised_values}; -$query .= qq{ LEFT JOIN authorised_values_branches ON ( id = av_id ) } if $branch_limit; -$query .= qq{ WHERE category = ?}; -$query .= qq{ AND ( branchcode = ? OR branchcode IS NULL ) } if $branch_limit; -$query .= qq{ GROUP BY lib ORDER BY lib, lib_opac}; -my $authorised_values_sth = $dbh->prepare( $query ); - -my $branches = GetBranchesLoop(); # build once ahead of time, instead of multiple times later. - -# Adding a default choice, in case the user does not want to modify the branch -my $nochange_branch = { branchname => '', value => '', selected => 1 }; -unshift (@$branches, $nochange_branch); - -my $pref_itemcallnumber = C4::Context->preference('itemcallnumber'); - -# Getting list of subfields to keep when restricted batchmod edit is enabled -my $subfieldsToAllowForBatchmod = C4::Context->preference('SubfieldsToAllowForRestrictedBatchmod'); -my $allowAllSubfields = ( - not defined $subfieldsToAllowForBatchmod - or $subfieldsToAllowForBatchmod == q|| -) ? 1 : 0; -my @subfieldsToAllow = split(/ /, $subfieldsToAllowForBatchmod); - -foreach my $tag (sort keys %{$tagslib}) { - # loop through each subfield - foreach my $subfield (sort keys %{$tagslib->{$tag}}) { - next if IsMarcStructureInternal( $tagslib->{$tag}{$subfield} ); - next if (not $allowAllSubfields and $restrictededition && !grep { $tag . '$' . $subfield eq $_ } @subfieldsToAllow ); - next if ($tagslib->{$tag}->{$subfield}->{'tab'} ne "10"); - # barcode and stocknumber are not meant to be batch-modified - next if $tagslib->{$tag}->{$subfield}->{'kohafield'} eq 'items.barcode'; - next if $tagslib->{$tag}->{$subfield}->{'kohafield'} eq 'items.stocknumber'; - my %subfield_data; - - my $index_subfield = int(rand(1000000)); - if ($subfield eq '@'){ - $subfield_data{id} = "tag_".$tag."_subfield_00_".$index_subfield; - } else { - $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".$index_subfield; - } - $subfield_data{tag} = $tag; - $subfield_data{subfield} = $subfield; - $subfield_data{marc_lib} ="{$tag}->{$subfield}->{lib}."\">".$tagslib->{$tag}->{$subfield}->{lib}.""; - $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory}; - $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable}; - my ($x,$value); - $value =~ s/"/"/g; - if ( !$value && $use_default_values) { - $value = $tagslib->{$tag}->{$subfield}->{defaultvalue}; - # get today date & replace YYYY, MM, DD if provided in the default value - my $today = dt_from_string; - my $year = $today->year; - my $month = $today->month; - my $day = $today->day; - $value =~ s/YYYY/$year/g; - $value =~ s/MM/$month/g; - $value =~ s/DD/$day/g; - } - $subfield_data{visibility} = "display:none;" if (($tagslib->{$tag}->{$subfield}->{hidden} > 4) || ($tagslib->{$tag}->{$subfield}->{hidden} < -4)); - # testing branch value if IndependentBranches. - - if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) { - my @authorised_values; - my %authorised_lib; - # builds list, depending on authorised value... - - if ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "branches" ) { - foreach my $thisbranch (@$branches) { - push @authorised_values, $thisbranch->{value}; - $authorised_lib{$thisbranch->{value}} = $thisbranch->{branchname}; - } - $value = ""; - } - elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) { - push @authorised_values, ""; - my $itemtypes = GetItemTypes( style => 'array' ); - for my $itemtype ( @$itemtypes ) { - push @authorised_values, $itemtype->{itemtype}; - $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description}; - } - $value = ""; - - #---- class_sources - } - elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) { - push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} ); - - my $class_sources = GetClassSources(); - my $default_source = C4::Context->preference("DefaultClassificationSource"); - - foreach my $class_source (sort keys %$class_sources) { - next unless $class_sources->{$class_source}->{'used'} or - ($value and $class_source eq $value) or - ($class_source eq $default_source); - push @authorised_values, $class_source; - $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'}; - } - $value = ''; - - #---- "true" authorised value - } - else { - push @authorised_values, ""; # unless ( $tagslib->{$tag}->{$subfield}->{mandatory} ); - $authorised_values_sth->execute( $tagslib->{$tag}->{$subfield}->{authorised_value}, $branch_limit ? $branch_limit : () ); - while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) { - push @authorised_values, $value; - $authorised_lib{$value} = $lib; - } - $value=""; - } - $subfield_data{marc_value} = { - type => 'select', - id => "tag_".$tag."_subfield_".$subfield."_".$index_subfield, - name => "field_value", - values => \@authorised_values, - labels => \%authorised_lib, - default => $value, - }; - # it's a thesaurus / authority field - } - elsif ( $tagslib->{$tag}->{$subfield}->{authtypecode} ) { - $subfield_data{marc_value} = { - type => 'text1', - id => $subfield_data{id}, - value => $value, - authtypecode => $tagslib->{$tag}->{$subfield}->{authtypecode}, - } - } - elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) { # plugin - require Koha::FrameworkPlugin; - my $plugin = Koha::FrameworkPlugin->new( { - name => $tagslib->{$tag}->{$subfield}->{'value_builder'}, - item_style => 1, - }); - my $temp; - my $pars= { dbh => $dbh, record => $temp, tagslib => $tagslib, - id => $subfield_data{id}, tabloop => \@loop_data }; - $plugin->build( $pars ); - if( !$plugin->errstr ) { - $subfield_data{marc_value} = { - type => 'text2', - id => $subfield_data{id}, - value => $value, - javascript => $plugin->javascript, - noclick => $plugin->noclick, - }; - } else { - warn $plugin->errstr; - $subfield_data{marc_value} = { # supply default input form - type => 'text', - id => $subfield_data{id}, - value => $value, - }; - } - } - elsif ( $tag eq '' ) { # it's an hidden field - $subfield_data{marc_value} = { - type => 'hidden', - id => $subfield_data{id}, - value => $value, - }; - } - elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ? - $subfield_data{marc_value} = { - type => 'text', - id => $subfield_data{id}, - value => $value, - }; - } - elsif ( length($value) > 100 - or (C4::Context->preference("marcflavour") eq "UNIMARC" and - 300 <= $tag && $tag < 400 && $subfield eq 'a' ) - or (C4::Context->preference("marcflavour") eq "MARC21" and - 500 <= $tag && $tag < 600 ) - ) { - # oversize field (textarea) - $subfield_data{marc_value} = { - type => 'textarea', - id => $subfield_data{id}, - value => $value, - }; - } else { - # it's a standard field - $subfield_data{marc_value} = { - type => 'text', - id => $subfield_data{id}, - value => $value, - }; - } -# $subfield_data{marc_value}=""; - push (@loop_data, \%subfield_data); - $i++ - } -} # -- End foreach tag -$authorised_values_sth->finish; - + # now, build the item form for entering a new item + my @loop_data =(); + my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : ""; + + my $pref_itemcallnumber = C4::Context->preference('itemcallnumber'); + + # Getting list of subfields to keep when restricted batchmod edit is enabled + my @subfields_to_allow = $restrictededition ? split ' ', C4::Context->preference('SubfieldsToAllowForRestrictedBatchmod') : (); + + my $subfields = Koha::UI::Form::Builder::Item->new->edit_form( + { + restricted_editition => $restrictededition, + ( + @subfields_to_allow + ? ( subfields_to_allow => \@subfields_to_allow ) + : () + ), + subfields_to_ignore => ['items.barcode'], + prefill_with_default_values => $use_default_values, + default_branches_empty => 1, + } + ); # what's the next op ? it's what we are not in : an add if we're editing, otherwise, and edit. - $template->param(item => \@loop_data); - if (@notfoundbarcodes) { - my @notfoundbarcodesloop = map{{barcode=>$_}}@notfoundbarcodes; - $template->param(notfoundbarcodes => \@notfoundbarcodesloop); - } + $template->param( + subfields => $subfields, + notfoundbarcodes => \@notfoundbarcodes, + notfounditemnumbers => \@notfounditemnumbers + ); $nextop="action" } # -- End action="show" @@ -528,10 +473,12 @@ sub BuildItemsData{ my %witness; #---- stores the list of subfields used at least once, with the "meaning" of the code my @big_array; #---- finds where items.itemnumber is stored - my ( $itemtagfield, $itemtagsubfield) = &GetMarcFromKohaField("items.itemnumber", ""); - my ($branchtagfield, $branchtagsubfield) = &GetMarcFromKohaField("items.homebranch", ""); + my ( $itemtagfield, $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" ); + my ($branchtagfield, $branchtagsubfield) = &GetMarcFromKohaField( "items.homebranch" ); foreach my $itemnumber (@itemnumbers){ - my $itemdata=GetItem($itemnumber); + my $itemdata = Koha::Items->find($itemnumber); + next unless $itemdata; # Should have been tested earlier, but just in case... + $itemdata = $itemdata->unblessed; my $itemmarc=Item2Marc($itemdata); my %this_row; foreach my $field (grep {$_->tag() eq $itemtagfield} $itemmarc->fields()) { @@ -564,11 +511,14 @@ sub BuildItemsData{ # grab title, author, and ISBN to identify bib that the item # belongs to in the display - my $biblio=GetBiblioData($$itemdata{biblionumber}); - $this_row{title} = $biblio->{title}; - $this_row{author} = $biblio->{author}; - $this_row{isbn} = $biblio->{isbn}; - $this_row{biblionumber} = $biblio->{biblionumber}; + my $biblio = Koha::Biblios->find( $itemdata->{biblionumber} ); + $this_row{title} = $biblio->title; + $this_row{author} = $biblio->author; + $this_row{isbn} = $biblio->biblioitem->isbn; + $this_row{biblionumber} = $biblio->biblionumber; + $this_row{holds} = $biblio->holds->count; + $this_row{item_holds} = Koha::Holds->search( { itemnumber => $itemnumber } )->count; + $this_row{item} = Koha::Items->find($itemnumber); if (%this_row) { push(@big_array, \%this_row); @@ -592,13 +542,24 @@ sub BuildItemsData{ $row_data{title} = $row->{title}; $row_data{isbn} = $row->{isbn}; $row_data{biblionumber} = $row->{biblionumber}; + $row_data{holds} = $row->{holds}; + $row_data{item_holds} = $row->{item_holds}; + $row_data{item} = $row->{item}; + $row_data{safe_to_delete} = $row->{item}->safe_to_delete; my $is_on_loan = C4::Circulation::IsItemIssued( $row->{itemnumber} ); $row_data{onloan} = $is_on_loan ? 1 : 0; push(@item_value_loop,\%row_data); } my @header_loop=map { { header_value=> $witness{$_}} } @witnesscodessorted; - return { item_loop => \@item_value_loop, item_header_loop => \@header_loop }; + my @cannot_be_deleted = map { + $_->{safe_to_delete} == 1 ? () : $_->{item}->barcode + } @item_value_loop; + return { + item_loop => \@item_value_loop, + cannot_be_deleted => \@cannot_be_deleted, + item_header_loop => \@header_loop + }; } #BE WARN : it is not the general case @@ -608,10 +569,13 @@ sub BuildItemsData{ # And $tag>10 sub UpdateMarcWith { my ($marcfrom,$marcto)=@_; - my ( $itemtag, $itemtagsubfield) = &GetMarcFromKohaField("items.itemnumber", ""); + my ( $itemtag, $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" ); my $fieldfrom=$marcfrom->field($itemtag); my @fields_to=$marcto->field($itemtag); my $modified = 0; + + return $modified unless $fieldfrom; + foreach my $subfield ( $fieldfrom->subfields() ) { foreach my $field_to_update ( @fields_to ) { if ( $subfield->[1] ) { @@ -629,80 +593,31 @@ sub UpdateMarcWith { return $modified; } -sub find_value { - my ($tagfield,$insubfield,$record) = @_; - my $result; - my $indicator; - foreach my $field ($record->field($tagfield)) { - my @subfields = $field->subfields(); - foreach my $subfield (@subfields) { - if (@$subfield[0] eq $insubfield) { - $result .= @$subfield[1]; - $indicator = $field->indicator(1).$field->indicator(2); - } - } +sub apply_regex { + my ($params) = @_; + my $search = $params->{search}; + my $replace = $params->{replace}; + my $modifiers = $params->{modifiers} || []; + my $value = $params->{value}; + + my @available_modifiers = qw( i g ); + my $retained_modifiers = q||; + for my $modifier ( split //, @$modifiers ) { + $retained_modifiers .= $modifier + if grep { /$modifier/ } @available_modifiers; } - return($indicator,$result); -} - -# ---------------------------- -# Background functions - - -sub add_results_to_template { - my $template = shift; - my $results = shift; - $template->param(map { $_ => $results->{$_} } keys %{ $results }); -} - -sub add_saved_job_results_to_template { - my $template = shift; - my $completedJobID = shift; - my $job = C4::BackgroundJob->fetch($sessionID, $completedJobID); - my $results = $job->results(); - add_results_to_template($template, $results); - - my $fields = $job->get("modified_fields"); - my $items = $job->get("modified_items"); - $template->param( - modified_items => $items, - modified_fields => $fields, - ); -} - -sub put_in_background { - my $job_size = shift; - - my $job = C4::BackgroundJob->new($sessionID, "test", '/cgi-bin/koha/tools/batchMod.pl', $job_size); - my $jobID = $job->id(); - - # fork off - if (my $pid = fork) { - # parent - # return job ID as JSON - - # prevent parent exiting from - # destroying the kid's database handle - # FIXME: according to DBI doc, this may not work for Oracle - $dbh->{InactiveDestroy} = 1; - - my $reply = CGI->new(""); - print $reply->header(-type => 'text/html'); - print '{"jobID":"' . $jobID . '"}'; - exit 0; - } elsif (defined $pid) { - # child - # close STDOUT to signal to Apache that - # we're now running in the background - close STDOUT; - close STDERR; - } else { - # fork failed, so exit immediately - warn "fork failed while attempting to run tools/batchMod.pl as a background job"; - exit 0; + if ( $retained_modifiers =~ m/^(ig|gi)$/ ) { + $value =~ s/$search/$replace/ig; + } + elsif ( $retained_modifiers eq 'i' ) { + $value =~ s/$search/$replace/i; + } + elsif ( $retained_modifiers eq 'g' ) { + $value =~ s/$search/$replace/g; + } + else { + $value =~ s/$search/$replace/; } - return $job; -} - - + return $value; +}