@@ -, +, @@ Bug 5725 : Follow up fixing permissions thanks to Paul Poulain Bug 5725 follow up Owen noticed the batchedit was not working. This was because we made a patch months ago to change the GetBiblio API (the 1st returned arg is useless) This patch is not in main trunk, resulting in the error owen has noticed. This patch goes back to official API (even if it is clumsy : the patch we did does not apply anymore) Bug 5725 : MT #2991 (Feature) Batch biblio modifications This patch add a new feature of batch biblio modifications, it permit to select biblios from basket, and modify fields of them setting Rebased to fix whitespace issues. - Correcting page title - Adding breadcrums - Adding sidebar menu (matching other batch mod pages) - Showing "loading" graphic during AJAX operations - Adding empty option to list of MARC tags so that onchange event will fire when the user is choosing 000 - Other structural markup changes --- C4/Biblio.pm | 101 ++++++++ installer/data/mysql/updatedatabase.pl | 8 +- koha-tmpl/intranet-tmpl/prog/en/js/basket.js | 6 + .../intranet-tmpl/prog/en/modules/basket/basket.tt | 7 + .../prog/en/modules/tools/batchedit.tt | 257 ++++++++++++++++++++ .../prog/en/modules/tools/tools-home.tt | 5 + koha-tmpl/intranet-tmpl/prog/img/loading-small.gif | Bin 0 -> 1652 bytes tools/batchedit.pl | 221 +++++++++++++++++ 8 files changed, 604 insertions(+), 1 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/batchedit.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/img/loading-small.gif create mode 100755 tools/batchedit.pl --- a/C4/Biblio.pm +++ a/C4/Biblio.pm @@ -133,6 +133,7 @@ BEGIN { &TransformHtmlToXml &PrepareItemrecordDisplay &GetNoZebraIndexes + &BatchModField ); } @@ -3879,6 +3880,106 @@ sub GetHolds { } +=head3 BatchModField + + Mod subfields in field record + + returns 1,$record if succeed + returns 0,$record if Action was not processed + returns -1 if no record + returns -2 if no action done on record + +=cut + +sub BatchModField { + my ( $record, $field, $subfield, $action, $condval, $nocond, $repval ) = @_; + + return -1 unless $record; + $condval=NormalizeString($condval); + my $condition=qr/$condval/; + + if($action eq "add"){ + for my $rfield ($record->field($field)){ + $rfield->add_subfields( $subfield => $repval ); + } + return 1; + }elsif($action eq "addfield"){ + my $new_field = MARC::Field->new($field,'','', + $subfield => $repval); + $record->insert_fields_ordered($new_field); + return 1; + } else { + my $done=0; + if ($field eq '000') { + my $value=$record->leader(); + if ($value=~ s/$condition/$repval/){ + $record->leader($repval); + $done=1; + } + if ( $nocond eq 'true'){ + $record->leader($repval); + $done=1; + } + } + for my $rfield ($record->field($field)) { + if ($subfield && $subfield ne "@"){ + my @subfields = $rfield->subfields(); + my @subfields_to_add; + foreach my $subf (@subfields) { + if ($subf->[0] eq $subfield){ + $subf->[1]=NormalizeString($subf->[1]); + if ( $action eq "mod" ) { + if ( $nocond ne "true" && $subf->[1] =~ s/$condition/$repval/) { + $done=1; + } + if ($nocond eq "true"){ + $subf->[1] = $repval; + $done=1; + } + } elsif ( $action eq "del" ) { + if ( $subf->[1] =~ m/$condition/ || $nocond eq "true" ) { + $done=1; + next; + } + } + } + push @subfields_to_add,@$subf; + } + if ($done){ + if (@subfields_to_add){ + $rfield->replace_with(MARC::Field->new($rfield->tag,$rfield->indicator(1),$rfield->indicator(2),@subfields_to_add)); + } + else { + my $count= $record->delete_field($rfield); + } + } + } + else { + if ($action eq "del"){ + my $count=$record->delete_field($rfield); + $done=1; + } + else { + if ($field < 10){ + my $value=$record->field($field)->data(); + if ($value=~ s/$condition/$repval/){ + $record->field($field)->update($value); + $done=1; + + } + if ( $nocond eq 'true'){ + $record->field($field)->update($repval); + $done=1; + } + } + } + } + } + return ($done,$record); + } + return -2; +} + 1; __END__ --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -4364,7 +4364,7 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { $DBversion = "3.05.00.004"; if (C4::Context->preference("Version") < TransformToNum($DBversion)) { - $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ShowReviewerPhoto',1,'If ON, photo of reviewer will be shown beside comments in OPAC',NULL,'YesNo');"); + $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ShowReviewerPhoto',1,'If ON, photo of reviewer will be shown beside comments in OPAC',NULL,'YesNo')"); print "Upgrade to $DBversion done (Add syspref ShowReviewerPhoto)\n"; SetVersion($DBversion); } @@ -4435,6 +4435,12 @@ $DBversion = "3.05.00.010"; if (C4::Context->preference("Version") < TransformToNum($DBversion)) { $dbh->do("CREATE INDEX priorityfoundidx ON reserves (priority,found)"); print "Create an index on reserves to speed up holds awaiting pickup report bug 5866\n"; +} + +$DBversion = "3.05.00.XXX"; +if (C4::Context->preference("Version") < TransformToNum($DBversion)) { + $dbh->do("INSERT INTO permissions (module_bit, code, description) VALUES ( 13, 'batchedit', 'Batch editing biblios')"); + print "Upgrade to $DBversion done (batch edit permission)\n"; SetVersion($DBversion); } --- a/koha-tmpl/intranet-tmpl/prog/en/js/basket.js +++ a/koha-tmpl/intranet-tmpl/prog/en/js/basket.js @@ -438,4 +438,10 @@ $(document).ready(function(){ if(basketcount){ updateBasket(basketcount); } }); +function batchEdit(){ + var valCookie = readCookie(nameCookie); + var strCookie = nameParam + "=" + valCookie; + var loc = CGIBIN + "tools/batchedit.pl?" + strCookie; + window.opener.location = loc; +} --- a/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt @@ -86,6 +86,13 @@ function placeHold () { [% IF ( verbose ) %]Brief Display[% ELSE %]More Details[% END %] + [% IF ( CAN_user_tools_batchedit ) %] +
  • + + Batch Edit + +
  • + [% END %]
  • Send --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batchedit.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batchedit.tt @@ -0,0 +1,257 @@ +[% INCLUDE 'doc-head-open.inc' %] +Koha › Tools › Batch record of modification +[% INCLUDE 'doc-head-close.inc' %] + +[% UNLESS ( modsuccess ) %] + + +[% END %] + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + + +
    +
    +
    +
    + +
    +[% IF ( moddone ) %] +
    All operations processed
    +[% END %] + +[% UNLESS ( bib_list ) %] +

    Batch record modification

    +
    +
    +
    + Use a file of biblionumbers +
      +
    1. +
    +
    +
    +
    +
    +

    Reminder: You can also add records to your cart and reach this page from the cart.

    +
    +
    +
    +
    + Or enter records one by one +
      +
    1. + + +
    2. +
    + +
    +[% ELSE %] +

    List of records:

    + + + + [% IF ( moddone ) %] [% END %] + + + [% FOREACH biblioinfo IN biblioinfos %] + + + + + [% IF ( moddone ) %] + [% IF ( biblioinfo.OK ) %] + + [% END %] + + [% END %] +
    BiblionumberTitleAuthorStatus
    [% biblioinfo.biblionumber %][% biblionumber = biblioinfo.biblionumber %] [% INCLUDE 'biblio-default-view.inc' %][% IF ( biblioinfo.title ) %][% biblioinfo.title |html %][% ELSE %]No title[% END %][% biblioinfo.author %] + [% ELSE %] + + [% END %] + [% IF ( biblioinfo.OK ) %]OK[% END %] + [% IF ( biblioinfo.No_Actions ) %]Nothing done[% END %] + [% IF ( biblioinfo.Actions_Failed ) %] + Some actions failed. List follow :
      [% FOREACH failed_action IN biblioinfo.failed_actions %]
    • [% failed_action.action %]
    • [% END %]
    + [% END %] +
    +[% END %] + +[% UNLESS ( moddone ) %] + + [% IF ( bib_list ) %] +
    +

    Modification rules:

    + + + + + + + + + + + + + + + + + + +
    FieldSubfieldActionCondition ValueValue 
    + + + + + + + + + + +
    + +
    (don't forget to click "Add" button on the right of the previous form before submitting)
    +
    +
    + [% ELSE %] +
    + + [% END %] +[% END %] +
    +
    +
    + [% INCLUDE 'tools-menu.inc' %] +
    +
    +[% INCLUDE 'intranet-bottom.inc' %] --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt @@ -103,6 +103,11 @@
    Modify items in a batch
    [% END %] + [% IF ( CAN_user_tools_batchedit ) %] +
    Batch biblio modification
    +
    Modify biblios in a batch
    + [% END %] + [% IF ( CAN_user_tools_export_catalog ) %]
    Export bibliographic and holdings
    Export bibliographic and holdings data
    --- a/tools/batchedit.pl +++ a/tools/batchedit.pl @@ -0,0 +1,221 @@ +#!/usr/bin/perl + + +# Copyright 2010 SARL BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA + +use CGI; +use strict; +use C4::Output; +use C4::Auth; +use C4::Branch; +use C4::Koha; +use C4::Biblio; +use C4::Context; +use C4::Debug; +use JSON; + +my $input = new CGI; +my $dbh = C4::Context->dbh; + +my $filefh = $input->param('uploadfile'); +my $recordslist = $input->param('recordslist'); +my $bib_list = $input->param('bib_list'); +my @biblionumbers; + +#retrieve the list of biblionumber, from a file, a list entered manually or the basket +if ($filefh) { + while ( my $biblionumber = <$filefh> ) { + $biblionumber =~ s/[\r\n]*$//g; + push @biblionumbers, $biblionumber if $biblionumber; + } +} elsif ($recordslist) { + push @biblionumbers, split( /\s\n/, $recordslist ); +} elsif ($bib_list) { + push @biblionumbers, split('/', $bib_list); +} + +my $op = $input->param('op'); +my ($template, $loggedinuser, $cookie); + +my $frameworkcode=""; +my $tagslib = &GetMarcStructure(1,$frameworkcode); +my %report_actions; + +# if this script is called by ajax, buid an ajax/xml answer +if($input->param('field') and not defined $op){ + ($template, $loggedinuser, $cookie) + = get_template_and_user({template_name => "acqui/ajax.tmpl", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => { tools => "batchedit" }, + }); + + + my $tag = $input->param('field'); + my $subfield = $input->param('subfield'); + + if($input->param('subfield')){ + my $branches = GetBranchesLoop(); + + my @authorised_values; + if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) { + if ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "branches" ) { + foreach my $thisbranch (@$branches) { + push @authorised_values, { + code => $thisbranch->{value}, + value => $thisbranch->{branchname}, + }; + # $value = $thisbranch->{value} if $thisbranch->{selected}; + } + }elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) { + my $sth = $dbh->prepare("SELECT itemtype,description FROM itemtypes ORDER BY description"); + $sth->execute(); + while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) { + push @authorised_values, { + code => $itemtype, + value => $description, + }; + } + + }else { + # Getting the fields where the item location is + my ($location_field, $location_subfield) = GetMarcFromKohaField('items.location', $frameworkcode); + + # Getting the name of the authorised values' category for item location + my $item_location_category = $tagslib->{$location_field}->{$location_subfield}->{'authorised_value'}; + # Are we dealing with item location ? + my $item_location = ($tagslib->{$tag}->{$subfield}->{authorised_value} eq $item_location_category) ? 1 : 0; + + # If so, we sort by authorised_value, else by libelle + my $orderby = $item_location ? 'authorised_value' : 'lib'; + + my $authorised_values_sth = $dbh->prepare("SELECT authorised_value,lib FROM authorised_values WHERE category=? ORDER BY $orderby"); + + $authorised_values_sth->execute( $tagslib->{$tag}->{$subfield}->{authorised_value}); + + + while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) { + push @authorised_values, { + code => $value, + value => ($item_location) ? $value . " - " . $lib : $lib, + }; + + } + } + } + $template->param('return' => to_json(\@authorised_values)); + }else{ + my @modifiablesubf; + + foreach my $subfield (sort keys %{$tagslib->{$tag}}) { + next if subfield_is_koha_internal_p($subfield); + next if $subfield eq "@"; + next if ($tagslib->{$tag}->{$subfield}->{'tab'} eq "10"); + my %subfield_data; + $subfield_data{subfield} = $subfield; + push @modifiablesubf, \%subfield_data; + } + $template->param('return' => to_json(\@modifiablesubf)); + } + + + output_html_with_http_headers $input, $cookie, $template->output; + exit; +}else{ +# standard call (via web browser) + ($template, $loggedinuser, $cookie) + = get_template_and_user({template_name => "tools/batchedit.tmpl", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => { tools => "batchedit" }, + }); + + $template->param( bib_list => join('/', @biblionumbers)); + # if no $op, just display the form + if(!defined $op) { + my @modifiablefields; + + foreach my $tag (sort keys %{$tagslib}) { + my %subfield_data; + foreach my $subfield (sort keys %{$tagslib->{$tag}}) { + next if $subfield_data{tag}; + next if subfield_is_koha_internal_p($subfield); + next if ($tagslib->{$tag}->{$subfield}->{'tab'} eq "10"); + + $subfield_data{tag} = $tag; + + push @modifiablefields, \%subfield_data; + } + } + $template->param(marcfields => \@modifiablefields, + ); + + }else{ + my @fields = $input->param('field'); + my @subfields = $input->param('subfield'); + my @actions = $input->param('action'); + my @condvals = $input->param('condval'); + my @nocondvals = $input->param('nocondval'); + my @repvals = $input->param('repval'); + foreach my $biblionumber ( @biblionumbers ){ + my $record = GetMarcBiblio($biblionumber); + my ($count, $biblio) = GetBiblio($biblionumber); + my @failed_actions; + for(my $i = 0 ; $i < scalar(@fields) ; $i++ ){ + my $field = $fields[$i]; + my $subfield = $subfields[$i]; + my $action = $actions[$i]; + my $condval = $condvals[$i]; + my $nocond = $nocondvals[$i]; + my $repval = $repvals[$i]; + my ($result,$record) = BatchModField($record, $field, $subfield, $action, $condval, $nocond, $repval); + push @failed_actions, {action=>"$field $subfield $action ".($nocond eq "true"?"all":$condval)." $repval"} if ($result<=0); + } + if (@failed_actions == scalar(@fields)){ + $report_actions{$biblionumber}->{status}="No_Actions"; + } + elsif (@failed_actions>0 and @failed_actions < scalar(@fields)){ + $report_actions{$biblionumber}->{status}="Actions_Failed"; + $report_actions{$biblionumber}->{failed_actions}=\@failed_actions; + } + elsif (@failed_actions == 0){ + $report_actions{$biblionumber}->{status}="OK"; + } + ModBiblio($record, $biblionumber, $biblio->{frameworkcode}); + } + $template->param('moddone' => 1); + } + +} + +my @biblioinfos; + +for my $biblionumber (@biblionumbers){ + my ($count,$biblio) = GetBiblio($biblionumber); + if (defined $op){ + $biblio->{$report_actions{$biblionumber}->{status}}=1; + $biblio->{failed_actions}=$report_actions{$biblionumber}->{failed_actions}; + } + push @biblioinfos, $biblio; +} + +$template->param(biblioinfos => \@biblioinfos); +output_html_with_http_headers $input, $cookie, $template->output; +exit; --