Bug 4935: fix for allowing authorized values to be "unset"
[koha.git] / tools / batchMod.pl
index f7e9557..6a702dc 100755 (executable)
@@ -28,11 +28,10 @@ use C4::Items;
 use C4::Context;
 use C4::Koha; # XXX subfield_is_koha_internal_p
 use C4::Branch; # XXX subfield_is_koha_internal_p
+use C4::BackgroundJob;
 use C4::ClassSource;
 use C4::Dates;
 use C4::Debug;
-use YAML;
-use Switch;
 use MARC::File::XML;
 
 my $input = new CGI;
@@ -41,6 +40,9 @@ my $error        = $input->param('error');
 my @itemnumbers  = $input->param('itemnumber');
 my $op           = $input->param('op');
 my $del          = $input->param('del');
+my $completedJobID = $input->param('completedJobID');
+my $runinbackground = $input->param('runinbackground');
+
 
 my $template_name;
 my $template_flag;
@@ -49,7 +51,7 @@ if (!defined $op) {
     $template_flag = { tools => '*' };
 } else {
     $template_name = ($del) ? "tools/batchMod-del.tmpl" : "tools/batchMod-edit.tmpl";
-    $template_flag = ($del) ? { tools => 'batchdel' }   : { tools => 'batchmod' };
+    $template_flag = ($del) ? { tools => 'items_batchdel' }   : { tools => 'items_batchmod' };
 }
 
 
@@ -77,6 +79,10 @@ my $deleted_items = 0;     # Numbers of deleted items
 my $not_deleted_items = 0; # Numbers of items that could not be deleted
 my @not_deleted;           # List of the itemnumbers that could not be deleted
 
+my %cookies = parse CGI::Cookie($cookie);
+my $sessionID = $cookies{'CGISESSID'}->value;
+
+
 #--- ----------------------------------------------------------------------------
 if ($op eq "action") {
 #-------------------------------------------------------------------------------
@@ -87,94 +93,127 @@ if ($op eq "action") {
     my @ind_tag   = $input->param('ind_tag');
     my @indicator = $input->param('indicator');
 
-    my $xml = TransformHtmlToXml(\@tags,\@subfields,\@values,\@indicator,\@ind_tag, 'ITEM');
-    my $marcitem = MARC::Record::new_from_xml($xml, 'UTF-8');
-    my $localitem = TransformMarcToKoha( $dbh, $marcitem, "", 'items' );
-    foreach my $itemnumber(@itemnumbers){
-           my $itemdata=GetItem($itemnumber);
-           if ($input->param("del")){
-                   my $return = DelItemCheck(C4::Context->dbh, $itemdata->{'biblionumber'}, $itemdata->{'itemnumber'});
-                   if ($return == 1) {
-                       $deleted_items++;
-                   } else {
-                       $not_deleted_items++;
-                       push @not_deleted, { itemnumber => $itemdata->{'itemnumber'}, barcode => $itemdata->{'barcode'}, title => $itemdata->{'title'}, $return => 1 };
-                   }
-           } else {
-                   my $localmarcitem=Item2Marc($itemdata);
-                   UpdateMarcWith($marcitem,$localmarcitem);
-                   eval{my ($oldbiblionumber,$oldbibnum,$oldbibitemnum) = ModItemFromMarc($localmarcitem,$itemdata->{biblionumber},$itemnumber)};
-           }
-    }
-    # If we have a reasonable amount of items, we display them
-    if (scalar(@itemnumbers) <= 1000) {
-       $items_display_hashref=BuildItemsData(@itemnumbers);
+    # 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 $something_to_modify = scalar(grep {!/^$/} @values);
+
+    # Once the job is done
+    if ($completedJobID) {
+       # If we have a reasonable amount of items, we display them
+       if (scalar(@itemnumbers) <= 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);
+
+    # While the job is getting done
     } else {
-       # Else, we only display the barcode
-       my @simple_items_display = map {{ itemnumber => $_, barcode => GetBarcodeFromItemnumber($_), biblionumber => GetBiblionumberFromItemnumber($_) }} @itemnumbers;
-       $template->param("simple_items_display" => \@simple_items_display);
+
+       # Job size is the number of items we have to process
+       my $job_size = scalar(@itemnumbers);
+       my $job = undef;
+       my $callback = sub {};
+
+       # If we asked for background processing
+       if ($runinbackground) {
+           $job = put_in_background($job_size);
+           $callback = progress_callback($job, $dbh);
+       }
+
+       # For each item
+       my $i = 1; 
+       foreach my $itemnumber(@itemnumbers){
+
+               $job->progress($i) if $runinbackground;
+               my $itemdata=GetItem($itemnumber);
+               if ($input->param("del")){
+                       my $return = DelItemCheck(C4::Context->dbh, $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 };
+                       }
+               } else {
+                   if ($something_to_modify) {
+                       my $xml = TransformHtmlToXml(\@tags,\@subfields,\@values,\@indicator,\@ind_tag, 'ITEM');
+                       my $marcitem = MARC::Record::new_from_xml($xml, 'UTF-8');
+                       my $localitem = TransformMarcToKoha( $dbh, $marcitem, "", 'items' );
+                       my $localmarcitem=Item2Marc($itemdata);
+                       UpdateMarcWith($marcitem,$localmarcitem);
+                       eval{my ($oldbiblionumber,$oldbibnum,$oldbibitemnum) = ModItemFromMarc($localmarcitem,$itemdata->{biblionumber},$itemnumber)};
+                   }
+               }
+               $i++;
+       }
     }
 }
-
 #
 #-------------------------------------------------------------------------------
 # build screen with existing items. and "new" one
 #-------------------------------------------------------------------------------
 
 if ($op eq "show"){
-       my $filefh = $input->upload('uploadfile');
-       my $filecontent = $input->param('filecontent');
-       my @notfoundbarcodes;
+    my $filefh = $input->upload('uploadfile');
+    my $filecontent = $input->param('filecontent');
+    my @notfoundbarcodes;
 
     my @contentlist;
     if ($filefh){
         while (my $content=<$filefh>){
-            chomp $content;
+            $content =~ s/[\r\n]*$//;
             push @contentlist, $content if $content;
         }
 
-       switch ($filecontent) {
-           case "barcode_file" {
-               foreach my $barcode (@contentlist) {
-
-                   my $itemnumber = GetItemnumberFromBarcode($barcode);
-                   if ($itemnumber) {
-                       push @itemnumbers,$itemnumber;
-                   } else {
-                       push @notfoundbarcodes, $barcode;
-                   }
-               }
-
-           }
+        if ($filecontent eq 'barcode_file') {
+            foreach my $barcode (@contentlist) {
 
-           case "itemid_file" {
-               @itemnumbers = @contentlist;
-           }
-       }
+                my $itemnumber = GetItemnumberFromBarcode($barcode);
+                if ($itemnumber) {
+                    push @itemnumbers,$itemnumber;
+                } else {
+                    push @notfoundbarcodes, $barcode;
+                }
+            }
+        }
+        elsif ( $filecontent eq 'itemid_file') {
+            @itemnumbers = @contentlist;
+        }
     } else {
-       if ( my $list=$input->param('barcodelist')){
-        push my @barcodelist, split(/\s\n/, $list);
+        if ( my $list=$input->param('barcodelist')){
+            push my @barcodelist, split(/\s\n/, $list);
 
-       foreach my $barcode (@barcodelist) {
+            foreach my $barcode (@barcodelist) {
 
-           my $itemnumber = GetItemnumberFromBarcode($barcode);
-           if ($itemnumber) {
-               push @itemnumbers,$itemnumber;
-           } else {
-               push @notfoundbarcodes, $barcode;
-           }
-       }
+                my $itemnumber = GetItemnumberFromBarcode($barcode);
+                if ($itemnumber) {
+                    push @itemnumbers,$itemnumber;
+                } else {
+                    push @notfoundbarcodes, $barcode;
+                }
+            }
 
+        }
     }
-}
+
+    # 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 1000
     if (scalar(@itemnumbers) <= 1000) {
-       $items_display_hashref=BuildItemsData(@itemnumbers);
+        $items_display_hashref=BuildItemsData(@itemnumbers);
     } else {
-       $template->param("too_many_items" => scalar(@itemnumbers));
-       # Even if we do not display the items, we need the itemnumbers
-       my @itemnumbers_hashref = map {{itemnumber => $_}} @itemnumbers;
-       $template->param("itemnumbers_hashref" => \@itemnumbers_hashref);
+        $template->param("too_many_items" => scalar(@itemnumbers));
+        # Even if we do not display the items, we need the itemnumbers
+        my @itemnumbers_hashref = map {{itemnumber => $_}} @itemnumbers;
+        $template->param("itemnumbers_hashref" => \@itemnumbers_hashref);
     }
 # now, build the item form for entering a new item
 my @loop_data =();
@@ -182,6 +221,11 @@ my $i=0;
 my $authorised_values_sth = $dbh->prepare("SELECT authorised_value,lib FROM authorised_values WHERE category=? ORDER BY lib");
 
 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');
 
 
@@ -233,17 +277,18 @@ foreach my $tag (sort keys %{$tagslib}) {
            foreach my $thisbranch (@$branches) {
                push @authorised_values, $thisbranch->{value};
                $authorised_lib{$thisbranch->{value}} = $thisbranch->{branchname};
-               $value = $thisbranch->{value} if $thisbranch->{selected};
            }
+        $value = "";
        }
        elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
-           push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+           push @authorised_values, "";
            my $sth = $dbh->prepare("select itemtype,description from itemtypes order by description");
            $sth->execute;
            while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
                push @authorised_values, $itemtype;
                $authorised_lib{$itemtype} = $description;
            }
+        $value = "";
 
           #---- class_sources
       }
@@ -260,7 +305,7 @@ foreach my $tag (sort keys %{$tagslib}) {
               push @authorised_values, $class_source;
               $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
           }
-                 $value = $default_source unless ($value);
+                 $value = '';
 
           #---- "true" authorised value
       }
@@ -444,7 +489,7 @@ sub BuildItemsData{
 # Where subfield is not repeated
 # And where we are sure that field should correspond
 # And $tag>10
-sub UpdateMarcWith($$){
+sub UpdateMarcWith {
   my ($marcfrom,$marcto)=@_;
   #warn "FROM :",$marcfrom->as_formatted;
        my (  $itemtag,   $itemtagsubfield) = &GetMarcFromKohaField("items.itemnumber", "");
@@ -452,7 +497,7 @@ sub UpdateMarcWith($$){
        my @fields_to=$marcto->field($itemtag);
     foreach my $subfield ($fieldfrom->subfields()){
                foreach my $field_to_update (@fields_to){
-                               $field_to_update->update($$subfield[0]=>$$subfield[1]) if ($$subfield[1]);
+                               $field_to_update->update($$subfield[0]=>$$subfield[1]) if ($$subfield[1] != '' or $$subfield[1] == '0');
                }
     }
   #warn "TO edited:",$marcto->as_formatted;
@@ -474,5 +519,65 @@ sub find_value {
     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);
+}
+
+sub put_in_background {
+    my $job_size = shift;
+
+    my $job = C4::BackgroundJob->new($sessionID, "test", $ENV{'SCRIPT_NAME'}, $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 $ENV{'SCRIPT_NAME'} as a background job";
+        exit 0;
+    }
+    return $job;
+}
+
+sub progress_callback {
+    my $job = shift;
+    my $dbh = shift;
+    return sub {
+        my $progress = shift;
+        $job->progress($progress);
+    }
+}