--- loncom/homework/grades.pm	2008/02/06 16:32:30	1.510
+++ loncom/homework/grades.pm	2008/05/14 16:36:31	1.520
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.510 2008/02/06 16:32:30 raeburn Exp $
+# $Id: grades.pm,v 1.520 2008/05/14 16:36:31 www Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -47,8 +47,91 @@ use LONCAPA;
 use POSIX qw(floor);
 
 
+
 my %perm=();
 
+#  These variables are used to recover from ssi errors
+
+my $ssi_retries = 5;
+my $ssi_error;
+my $ssi_error_resource;
+my $ssi_error_message;
+
+
+#  Do an ssi with retries:
+#  While I'd love to factor out this with the vesrion in lonprintout,
+#  that would either require a data coupling between modules, which I refuse to perpetuate
+#  (there's quite enough of that already), or would require the invention of another infrastructure
+#  I'm not quite ready to invent (e.g. an ssi_with_retry object).
+#
+# At least the logic that drives this has been pulled out into loncommon.
+
+
+#
+#   ssi_with_retries - Does the server side include of a resource.
+#                      if the ssi call returns an error we'll retry it up to
+#                      the number of times requested by the caller.
+#                      If we still have a proble, no text is appended to the
+#                      output and we set some global variables.
+#                      to indicate to the caller an SSI error occurred.  
+#                      All of this is supposed to deal with the issues described
+#                      in LonCAPA BZ 5631 see:
+#                      http://bugs.lon-capa.org/show_bug.cgi?id=5631
+#                      by informing the user that this happened.
+#
+# Parameters:
+#   resource   - The resource to include.  This is passed directly, without
+#                interpretation to lonnet::ssi.
+#   form       - The form hash parameters that guide the interpretation of the resource
+#                
+#   retries    - Number of retries allowed before giving up completely.
+# Returns:
+#   On success, returns the rendered resource identified by the resource parameter.
+# Side Effects:
+#   The following global variables can be set:
+#    ssi_error                - If an unrecoverable error occurred this becomes true.
+#                               It is up to the caller to initialize this to false
+#                               if desired.
+#    ssi_error_resource  - If an unrecoverable error occurred, this is the value
+#                               of the resource that could not be rendered by the ssi
+#                               call.
+#    ssi_error_message   - The error string fetched from the ssi response
+#                               in the event of an error.
+#
+sub ssi_with_retries {
+    my ($resource, $retries, %form) = @_;
+    my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
+    if ($response->is_error) {
+	$ssi_error          = 1;
+	$ssi_error_resource = $resource;
+	$ssi_error_message  = $response->code . " " . $response->message;
+    }
+
+    return $content;
+
+}
+#
+#  Prodcuces an ssi retry failure error message to the user:
+#
+
+sub ssi_print_error {
+    my ($r) = @_;
+    my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
+    $r->print('
+<br />
+<h2>'.&mt('An unrecoverable network error occurred:').'</h2>
+<p>
+'.&mt('Unable to retrieve a resource from a server:').'<br />
+'.&mt('Resource:').' '.$ssi_error_resource.'<br />
+'.&mt('Error:').' '.$ssi_error_message.'
+</p>
+<p>'.
+&mt('It is recommended that you try again later, as this error may mean the server was just temporarily unavailable, or is down for maintenance.').'<br />'.
+&mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
+'</p>');
+    return;
+}
+
 #
 # --- Retrieve the parts from the metadata file.---
 sub getpartlist {
@@ -201,13 +284,13 @@ sub reset_caches {
 
 	my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
 	$url=&Apache::lonnet::clutter($url);
-	my $subresult=&Apache::lonnet::ssi($url,
-					   ('grade_target' => 'analyze'),
-					   ('grade_domain' => $udom),
-					   ('grade_symb' => $symb),
-					   ('grade_courseid' => 
-					    $env{'request.course.id'}),
-					   ('grade_username' => $uname));
+	my $subresult=&ssi_with_retries($url, $ssi_retries,
+					   ('grade_target' => 'analyze',
+					    'grade_domain' => $udom,
+					    'grade_symb' => $symb,
+					    'grade_courseid' => 
+					    $env{'request.course.id'},
+					    'grade_username' => $uname));
 	(undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
 	my %analyze=&Apache::lonnet::str2hash($subresult);
 	return $analyze_cache{$key} = \%analyze;
@@ -1759,9 +1842,9 @@ sub download_all_link {
 	join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
 
     my $identifier = &Apache::loncommon::get_cgi_id();
-    &Apache::lonnet::appenv('cgi.'.$identifier.'.students' => $all_students,
-                            'cgi.'.$identifier.'.symb' => $symb,
-                            'cgi.'.$identifier.'.parts' => $parts,);
+    &Apache::lonnet::appenv({'cgi.'.$identifier.'.students' => $all_students,
+                             'cgi.'.$identifier.'.symb' => $symb,
+                             'cgi.'.$identifier.'.parts' => $parts,});
     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
 	      &mt('Download All Submitted Documents').'</a>');
     return
@@ -2685,7 +2768,7 @@ sub check_and_remove_from_queue {
 
 sub handback_files {
     my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
-    my $portfolio_root = &propath($domain,$stuname).'/userfiles/portfolio';
+    my $portfolio_root = '/userfiles/portfolio';
     my ($partlist,$handgrade,$responseType) = &response_type($symb);
 
     my @part_response_id = &flatten_responseType($responseType);
@@ -2703,7 +2786,8 @@ sub handback_files {
                     my ($answer_name,$answer_ver,$answer_ext) =
 		        &file_name_version_ext($answer_file);
 		    my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
-		    my @dir_list = &Apache::lonnet::dirlist($portfolio_path,$domain,$stuname,$portfolio_root);
+                    my $getpropath = 1;
+		    my @dir_list = &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,$domain,$stuname,$getpropath);
 		    my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                     # fix file name
                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
@@ -2839,8 +2923,7 @@ sub version_portfiles {
     my $version_parts = join('|',@$v_flag);
     my @returned_keys;
     my $parts = join('|', @$parts_graded);
-    my $portfolio_root = &propath($domain,$stu_name).
-	'/userfiles/portfolio';
+    my $portfolio_root = '/userfiles/portfolio';
     foreach my $key (keys(%$record)) {
         my $new_portfiles;
         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
@@ -2851,7 +2934,8 @@ sub version_portfiles {
                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
 		my ($answer_name,$answer_ver,$answer_ext) =
 		    &file_name_version_ext($answer_file);
-                my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);
+                my $getpropath = 1;    
+                my @dir_list = &Apache::lonnet::dirlist($portfolio_root.$directory,$domain,$stu_name,$getpropath);
                 my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                 my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
                 if ($new_answer ne 'problem getting file') {
@@ -4751,8 +4835,9 @@ sub get_response_bubbles {
 sub scantron_filenames {
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
+    my $getpropath = 1;
     my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
-				    &propath($cdom,$cname));
+                                       $getpropath);
     my @possiblenames;
     foreach my $filename (sort(@files)) {
 	($filename)=split(/&/,$filename);
@@ -4795,19 +4880,76 @@ sub scantron_uploads {
 =cut
 
 sub scantron_scantab {
-    my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
     my $result='<select name="scantron_format">'."\n";
     $result.='<option></option>'."\n";
-    foreach my $line (<$fh>) {
-	my ($name,$descrip)=split(/:/,$line);
-	if ($name =~ /^\#/) { next; }
-	$result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
+    my @lines = &get_scantronformat_file();
+    if (@lines > 0) {
+        foreach my $line (@lines) {
+            next if (($line =~ /^\#/) || ($line eq ''));
+	    my ($name,$descrip)=split(/:/,$line);
+	    $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
+        }
     }
     $result.='</select>'."\n";
-
     return $result;
 }
 
+=pod
+
+=item get_scantronformat_file
+
+  Returns an array containing lines from the scantron format file for
+  the domain of the course.
+
+  If a url for a custom.tab file is listed in domain's configuration.db, 
+  lines are from this file.
+
+  Otherwise, if a default.tab has been published in RES space by the 
+  domainconfig user, lines are from this file.
+
+  Otherwise, fall back to getting lines from the legacy file on the
+  local server:  /home/httpd/lonTabs/default_scantronformat.tab    
+
+=cut
+
+sub get_scantronformat_file {
+    my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
+    my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$cdom);
+    my $gottab = 0;
+    my @lines;
+    if (ref($domconfig{'scantron'}) eq 'HASH') {
+        if ($domconfig{'scantron'}{'scantronformat'} ne '') {
+            my $formatfile = &Apache::lonnet::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.$domconfig{'scantron'}{'scantronformat'});
+            if ($formatfile ne '-1') {
+                @lines = split("\n",$formatfile,-1);
+                $gottab = 1;
+            }
+        }
+    }
+    if (!$gottab) {
+        my $confname = $cdom.'-domainconfig';
+        my $default = $Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$cdom.'/'.$confname.'/default.tab';
+        my $formatfile =  &Apache::lonnet::getfile($default);
+        if ($formatfile ne '-1') {
+            @lines = split("\n",$formatfile,-1);
+            $gottab = 1;
+        }
+    }
+    if (!$gottab) {
+        my @domains = &Apache::lonnet::current_machine_domains();
+        if (grep(/^\Q$cdom\E$/,@domains)) {
+            my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
+            @lines = <$fh>;
+            close($fh);
+        } else {
+            my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab');
+            @lines = <$fh>;
+            close($fh);
+        }
+    }
+    return @lines;
+}
+
 =pod 
 
 =item scantron_CODElist
@@ -4881,6 +5023,8 @@ sub scantron_selectphase {
     my $CODE_unique=&scantron_CODEunique();
     my $result;
 
+    $ssi_error = 0;
+
     # Chunk of form to prompt for a file to grade and how:
 
     $result.= '
@@ -5064,10 +5208,10 @@ sub scantron_selectphase {
 
 sub get_scantron_config {
     my ($which) = @_;
-    my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
+    my @lines = &get_scantronformat_file();
     my %config;
     #FIXME probably should move to XML it has already gotten a bit much now
-    foreach my $line (<$fh>) {
+    foreach my $line (@lines) {
 	my ($name,$descrip)=split(/:/,$line);
 	if ($name ne $which ) { next; }
 	chomp($line);
@@ -6037,8 +6181,7 @@ sub scantron_validate_file {
     }
     if (!$stop) {
 	my $warning=&scantron_warning_screen('Start Grading');
-	$r->print('
-<b>'.&mt('Validation process complete.').'<b><br />
+	$r->print(&mt('Validation process complete.').'<br />
 '.$warning.'
 <input type="submit" name="submit" value="'.&mt('Start Grading').'" />
 <input type="hidden" name="command" value="scantron_process" />
@@ -7156,24 +7299,27 @@ sub scantron_get_maxbubble {
     my $response_number = 0;
     my $bubble_line     = 0;
     foreach my $resource (@resources) {
+        my $symb = $resource->symb();
         # Need to retrieve part IDs and response IDs because essayresponse,
         # reactionresponse and organicresponse items are not included in 
         # $analysis{'parts'} from lonnet::ssi.  
         my %possible_part_ids; 
         if (ref($resource->parts()) eq 'ARRAY') { 
             foreach my $part (@{$resource->parts()}) {
-                my @resp_ids = $resource->responseIds($part);
-                foreach my $id (@resp_ids) {
-                    $possible_part_ids{$part.'.'.$id} = 1;
+                if (!&Apache::loncommon::check_if_partid_hidden($part,$symb,$udom,$uname)) {
+                    my @resp_ids = $resource->responseIds($part);
+                    foreach my $id (@resp_ids) {
+                        $possible_part_ids{$part.'.'.$id} = 1;
+                    }
                 }
             }
         }
-	my $result=&Apache::lonnet::ssi($resource->src(),
-					('symb' => $resource->symb()),
-					('grade_target' => 'analyze'),
-					('grade_courseid' => $cid),
-					('grade_domain' => $udom),
-					('grade_username' => $uname));
+	my $result=&ssi_with_retries($resource->src(), $ssi_retries,
+					('symb' => $symb,
+					 'grade_target' => 'analyze',
+					 'grade_courseid' => $cid,
+					 'grade_domain' => $udom,
+					 'grade_username' => $uname));
 	my (undef, $an) =
 	    split(/_HASH_REF__/,$result, 2);
 
@@ -7182,7 +7328,12 @@ sub scantron_get_maxbubble {
 	my %analysis = &Apache::lonnet::str2hash($an);
 
         if (ref($analysis{'parts'}) eq 'ARRAY') {
-            @parts = @{$analysis{'parts'}};
+            foreach my $part (@{$analysis{'parts'}}) {
+                my ($id,$respid) = split(/\./,$part);
+                if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) {
+                    push(@parts,$part);
+                }
+            }
         }
         # Add part_ids for any essayresponse items. 
         foreach my $part_id (keys(%possible_part_ids)) {
@@ -7342,9 +7493,12 @@ sub scantron_validate_missingbubbles {
 
 sub scantron_process_students {
     my ($r) = @_;
+
     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
     my ($symb)=&get_symb($r);
-    if (!$symb) {return '';}
+    if (!$symb) {
+	return '';
+    }
     my $default_form_data=&defaultFormData($symb);
 
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
@@ -7365,6 +7519,7 @@ SCANTRONFORM
     my @delayqueue;
     my %completedstudents;
     
+    my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));
     my $count=&get_todo_count($scanlines,$scan_data);
     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',
  				    'Scantron Progress',$count,
@@ -7376,6 +7531,18 @@ SCANTRONFORM
     my ($uname,$udom,$started);
 
     &scantron_get_maxbubble();	# Need the bubble lines array to parse.
+    
+
+    # If an ssi failed in scantron_get_maxbubble, put an error message out to
+    # the user and return.
+
+    if ($ssi_error) {
+	$r->print("</form>");
+	&ssi_print_error($r);
+	$r->print(&show_grading_menu_form($symb));
+        &Apache::lonnet::remove_lock($lock);
+	return '';		# Dunno why the other returns return '' rather than just returning.
+    }
 
     while ($i<$scanlines->{'count'}) {
  	($uname,$udom)=('','');
@@ -7403,7 +7570,7 @@ SCANTRONFORM
   	($uname,$udom)=split(/:/,$uname);
 
 	&Apache::lonxml::clear_problem_counter();
-  	&Apache::lonnet::appenv(%$scan_record);
+  	&Apache::lonnet::appenv($scan_record);
 
 	if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
 	    &scantron_putfile($scanlines,$scan_data);
@@ -7424,10 +7591,17 @@ SCANTRONFORM
 		$form{'CODE'}=$scan_record->{'scantron.CODE'};
 	    } else {
 		$form{'CODE'}='';
+	    } 
+	    my $result=&ssi_with_retries($resource->src(), $ssi_retries, %form);
+	    if ($ssi_error) {
+		$ssi_error = 0;	# So end of handler error message does not trigger.
+		$r->print("</form>");
+		&ssi_print_error($r);
+		$r->print(&show_grading_menu_form($symb));
+                &Apache::lonnet::remove_lock($lock);
+		return '';	# Why return ''?  Beats me.
 	    }
-	    my $result=&Apache::lonnet::ssi($resource->src(),%form);
-	    if ($result ne '') {
-	    }
+
 	    if (&Apache::loncommon::connection_aborted($r)) { last; }
 	}
 	$completedstudents{$uname}={'line'=>$line};
@@ -7437,6 +7611,7 @@ SCANTRONFORM
 	&Apache::lonnet::delenv('scantron\.');
     }
     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+    &Apache::lonnet::remove_lock($lock);
 #    my $lasttime = &Time::HiRes::time()-$start;
 #    $r->print("<p>took $lasttime</p>");
 
@@ -7729,14 +7904,12 @@ sub grading_menu {
                 $menudata->{'url'}.'" >'.
                 $menudata->{'name'}."</a></h3>\n";
         } else {
-            $Str .='    <h3><input type="button" value="'.&mt('Verify Receipt').'" '.
+            $Str .='<hr /><input type="button" value="'.&mt('Verify Receipt').'" '.
                 $menudata->{'jscript'}.
                 ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
-                ' /></h3>';
-            $Str .= ('&nbsp;'x8).
-		&mt(' receipt: [_1]',
-		    &Apache::lonnet::recprefix($env{'request.course.id'}).
-                    '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />');
+                ' /> '.
+		&Apache::lonnet::recprefix($env{'request.course.id'}).
+                    '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />';
         }
         $Str .= '    '.('&nbsp;'x8).$menudata->{'short_description'}.
             "\n";
@@ -8469,7 +8642,7 @@ sub handler {
 	&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
     }
 
-
+    $ssi_error = 0;
     $request->print(&Apache::loncommon::start_page('Grading'));
     if ($symb eq '' && $command eq '') {
 	if ($env{'user.adv'}) {
@@ -8482,7 +8655,7 @@ sub handler {
 		if ($tsymb) {
 		    my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
 		    if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
-			$request->print(&Apache::lonnet::ssi_body('/res/'.$url,
+			$request->print(&ssi_with_retries('/res/'.$url, $ssi_retries,
 					  ('grade_username' => $tuname,
 					   'grade_domain' => $tudom,
 					   'grade_courseid' => $tcrsid,
@@ -8569,6 +8742,9 @@ sub handler {
 	    $request->print("Access Denied ($command)");
 	}
     }
+    if ($ssi_error) {
+	&ssi_print_error($request);
+    }
     $request->print(&Apache::loncommon::end_page());
     &reset_caches();
     return '';