--- loncom/homework/grades.pm	2021/01/23 20:24:53	1.782
+++ loncom/homework/grades.pm	2023/04/02 03:16:28	1.793
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.782 2021/01/23 20:24:53 raeburn Exp $
+# $Id: grades.pm,v 1.793 2023/04/02 03:16:28 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1173,6 +1173,8 @@ LISTJAVASCRIPT
         my @sections;
         if ($env{'request.course.sec'} ne '') {
             @sections = ($env{'request.course.sec'});
+        } elsif ($env{'form.section'} eq '') {
+            @sections = ('all');
         } else {
             @sections = &Apache::loncommon::get_env_multiple('form.section');
         }
@@ -1214,7 +1216,7 @@ LISTJAVASCRIPT
 	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
     if (exists($env{'form.Status'})) {
-	$gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
+	$gradeTable .= '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n";
     } else {
         $gradeTable .= &Apache::lonhtmlcommon::row_closure()
                       .&Apache::lonhtmlcommon::row_title(&mt('Student Status'))
@@ -2204,7 +2206,7 @@ sub files_exist {
         my ($uname,$udom,$fullname) = split(/:/,$student);
         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
 					      $udom,$uname);
-        my ($string,$timestamp)= &get_last_submission(\%record);
+        my ($string)= &get_last_submission(\%record);
         foreach my $submission (@$string) {
             my ($partid,$respid) =
 		($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
@@ -2556,169 +2558,12 @@ sub submission {
     #             (3) All transactions (by date)
     #             (4) The whole record (with detailed information for all transactions)
 
-    my ($string,$timestamp)= &get_last_submission(\%record,$is_tool);
-
-    my $lastsubonly;
-
-    if ($$timestamp eq '') {
-        $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
-    } elsif ($is_tool) {
-        $lastsubonly =
-            '<div class="LC_grade_submissions_body">'
-           .'<b>'.&mt('Date Grade Passed Back:').'</b> '.$$timestamp."</div>\n";
-    } else {
-        $lastsubonly =
-            '<div class="LC_grade_submissions_body">'
-           .'<b>'.&mt('Date Submitted:').'</b> '.$$timestamp."\n";
-
-	my %seenparts;
-	my @part_response_id = &flatten_responseType($responseType);
-	foreach my $part (@part_response_id) {
-	    my ($partid,$respid) = @{ $part };
-	    my $display_part=&get_display_part($partid,$symb);
-	    if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
-		if (exists($seenparts{$partid})) { next; }
-		$seenparts{$partid}=1;
-                $request->print(
-                    '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
-                    ' <b>'.&mt('Collaborative submission by: [_1]',
-                               '<a href="javascript:viewSubmitter(\''.
-                               $env{"form.$uname:$udom:$partid:submitted_by"}.
-                               '\');" target="_self">'.
-                               $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a>').
-                    '<br />');
-		next;
-	    }
-	    my $responsetype = $responseType->{$partid}->{$respid};
-	    if (!exists($record{"resource.$partid.$respid.submission"})) {
-                $lastsubonly.="\n".'<div class="LC_grade_submission_part">'.
-                    '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
-                    ' <span class="LC_internal_info">'.
-                    '('.&mt('Response ID: [_1]',$respid).')'.
-                    '</span>&nbsp; &nbsp;'.
-	       	    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
-		next;
-	    }
-	    foreach my $submission (@$string) {
-		my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
-		if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
-		my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
-		# Similarity check
-                my $similar='';
-                my ($type,$trial,$rndseed);
-                if ($hide eq 'rand') {
-                    $type = 'randomizetry';
-                    $trial = $record{"resource.$partid.tries"};
-                    $rndseed = $record{"resource.$partid.rndseed"};
-                }
-	        if ($env{'form.checkPlag'}) {
-		    my ($oname,$odom,$ocrsid,$oessay,$osim)=
-		    &most_similar($uname,$udom,$symb,$subval);
-		    if ($osim) {
-			$osim=int($osim*100.0);
-                        if ($hide eq 'anon') {
-                            $similar='<hr /><span class="LC_warning">'.&mt("Essay was found to be similar to another essay submitted for this assignment.").'<br />'.
-                                     &mt('As the current submission is for an anonymous survey, no other details are available.').'</span><hr />';
-                        } else {
-			    $similar='<hr />';
-                            if ($essayurl eq 'lib/templates/simpleproblem.problem') {
-                                $similar .= '<h3><span class="LC_warning">'.
-                                            &mt('Essay is [_1]% similar to an essay by [_2]',
-                                                $osim,
-                                                &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')').
-                                            '</span></h3>';
-                            } else {
-                                my %old_course_desc;
-                                if ($ocrsid ne '') {
-                                    if (ref($coursedesc_by_cid{$ocrsid}) eq 'HASH') {
-                                        %old_course_desc = %{$coursedesc_by_cid{$ocrsid}};
-                                    } else {
-                                        my $args;
-                                        if ($ocrsid ne $env{'request.course.id'}) {
-                                            $args = {'one_time' => 1};
-                                        }
-                                        %old_course_desc =
-                                            &Apache::lonnet::coursedescription($ocrsid,$args);
-                                        $coursedesc_by_cid{$ocrsid} = \%old_course_desc;
-                                    }
-                                    $similar .=
-                                        '<h3><span class="LC_warning">'.
-                                        &mt('Essay is [_1]% similar to an essay by [_2] in course [_3] (course id [_4]:[_5])',
-                                            $osim,
-                                            &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')',
-                                            $old_course_desc{'description'},
-                                            $old_course_desc{'num'},
-                                            $old_course_desc{'domain'}).
-                                        '</span></h3>';
-                                } else {
-                                    $similar .=
-                                        '<h3><span class="LC_warning">'.
-                                        &mt('Essay is [_1]% similar to an essay by [_2] in an unknown course',
-                                            $osim,
-                                            &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')').
-                                        '</span></h3>';
-                                }
-                            }
-                            $similar .= '<blockquote><i>'.
-                                        &keywords_highlight($oessay).
-                                        '</i></blockquote><hr />';
-                        }
-	            }
-		}
-		my $order=&get_order($partid,$respid,$symb,$uname,$udom,
-                                     undef,$type,$trial,$rndseed);
-                if (($env{'form.lastSub'} eq 'lastonly') ||
-                    ($env{'form.lastSub'} eq 'datesub')  ||
-                    ($env{'form.lastSub'} =~ /^(last|all)$/)) {
-		    my $display_part=&get_display_part($partid,$symb);
-                    $lastsubonly.='<div class="LC_grade_submission_part">'.
-                        '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
-                        ' <span class="LC_internal_info">'.
-                        '('.&mt('Response ID: [_1]',$respid).')'.
-                        '</span>&nbsp; &nbsp;';
-		    my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
-		    if (@$files) {
-                        if ($hide eq 'anon') {
-                            $lastsubonly.='<br />'.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
-                        } else {
-                            $lastsubonly.='<br /><br />'.'<b>'.&mt('Submitted Files:').'</b>'
-                                        .'<br /><span class="LC_warning">';
-                            if(@$files == 1) {
-                                $lastsubonly .= &mt('Like all files provided by users, this file may contain viruses!');
-                            } else {
-                                $lastsubonly .= &mt('Like all files provided by users, these files may contain viruses!');
-                            }
-                            $lastsubonly .= '</span>';
-                            foreach my $file (@$files) {
-                                &Apache::lonnet::allowuploaded('/adm/grades',$file);
-                                $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" alt="" /> '.$file.'</a>';
-                            }
-                        }
-			$lastsubonly.='<br />';
-                    }
-                    if ($hide eq 'anon') {
-                        $lastsubonly.='<br /><b>'.&mt('Anonymous Survey').'</b>'; 
-                    } else {
-                        $lastsubonly.='<br /><b>'.&mt('Submitted Answer:').' </b>';
-                        if ($draft) {
-                            $lastsubonly.= ' <span class="LC_warning">'.&mt('Draft Copy').'</span>';
-                        }
-                        $subval =
-			    &cleanRecord($subval,$responsetype,$symb,$partid,
-					 $respid,\%record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
-                        if ($responsetype eq 'essay') {
-                            $subval =~ s{\n}{<br />}g;
-                        }
-                        $lastsubonly.=$subval."\n";
-                    }
-                    if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
-		    $lastsubonly.='</div>';
-		}
-            }
-	}
-	$lastsubonly.='</div>'."\n"; # End: LC_grade_submissions_body
-    }
+    my ($lastsubonly,$partinfo) =
+        &show_last_submission($uname,$udom,$symb,$essayurl,$responseType,$env{'form.lastSub'},
+                              $is_tool,$fullname,\%record,\%coursedesc_by_cid);
+    $request->print($partinfo);
     $request->print($lastsubonly);
+
     if ($env{'form.lastSub'} eq 'datesub') {
         my ($parts,$handgrade,$responseType) = &response_type($symb,\$res_error);
 	$request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
@@ -2842,6 +2687,186 @@ sub submission {
     return '';
 }
 
+sub show_last_submission {
+    my ($uname,$udom,$symb,$essayurl,$responseType,$viewtype,$is_tool,$fullname,
+        $record,$coursedesc_by_cid) = @_;
+    my ($string,$timestamp,$lastgradetime,$lastsubmittime) =
+        &get_last_submission($record,$is_tool);
+
+    my ($lastsubonly,$partinfo);
+    if ($timestamp eq '') {
+        $lastsubonly.='<div class="LC_grade_submissions_body">'.$string->[0].'</div>';
+    } elsif ($is_tool) {
+        $lastsubonly =
+            '<div class="LC_grade_submissions_body">'
+           .'<b>'.&mt('Date Grade Passed Back:').'</b> '.$timestamp."</div>\n";
+    } else {
+        my ($shownsubmdate,$showngradedate);
+        if ($lastsubmittime && $lastgradetime) {
+            $shownsubmdate = &Apache::lonlocal::locallocaltime($lastsubmittime);
+            if ($lastgradetime > $lastsubmittime) {
+                 $showngradedate = &Apache::lonlocal::locallocaltime($lastgradetime);
+             }
+        } else {
+            $shownsubmdate = $timestamp;
+        }
+        $lastsubonly =
+            '<div class="LC_grade_submissions_body">'
+           .'<b>'.&mt('Date Submitted:').'</b> '.$shownsubmdate."\n";
+        if ($showngradedate) {
+            $lastsubonly .= '<br /><b>'.&mt('Date Graded:').'</b> '.$showngradedate."\n";
+        }
+
+        my %seenparts;
+        my @part_response_id = &flatten_responseType($responseType);
+        foreach my $part (@part_response_id) {
+            my ($partid,$respid) = @{ $part };
+            my $display_part=&get_display_part($partid,$symb);
+            if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
+                if (exists($seenparts{$partid})) { next; }
+                $seenparts{$partid}=1;
+                $partinfo .=
+                    '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
+                    ' <b>'.&mt('Collaborative submission by: [_1]',
+                               '<a href="javascript:viewSubmitter(\''.
+                               $env{"form.$uname:$udom:$partid:submitted_by"}.
+                               '\');" target="_self">'.
+                               $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a>').
+                    '<br />';
+                next;
+            }
+            my $responsetype = $responseType->{$partid}->{$respid};
+            if (!exists($record->{"resource.$partid.$respid.submission"})) {
+                $lastsubonly.="\n".'<div class="LC_grade_submission_part">'.
+                    '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
+                    ' <span class="LC_internal_info">'.
+                    '('.&mt('Response ID: [_1]',$respid).')'.
+                    '</span>&nbsp; &nbsp;'.
+                    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
+                next;
+            }
+            foreach my $submission (@$string) {
+                my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
+                if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
+                my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
+                # Similarity check
+                my $similar='';
+                my ($type,$trial,$rndseed);
+                if ($hide eq 'rand') {
+                    $type = 'randomizetry';
+                    $trial = $record->{"resource.$partid.tries"};
+                    $rndseed = $record->{"resource.$partid.rndseed"};
+                }
+                if ($env{'form.checkPlag'}) {
+                    my ($oname,$odom,$ocrsid,$oessay,$osim)=
+                    &most_similar($uname,$udom,$symb,$subval);
+                    if ($osim) {
+                        $osim=int($osim*100.0);
+                        if ($hide eq 'anon') {
+                            $similar='<hr /><span class="LC_warning">'.&mt("Essay was found to be similar to another essay submitted for this assignment.").'<br />'.
+                                     &mt('As the current submission is for an anonymous survey, no other details are available.').'</span><hr />';
+                        } else {
+                            $similar='<hr />';
+                            if ($essayurl eq 'lib/templates/simpleproblem.problem') {
+                                $similar .= '<h3><span class="LC_warning">'.
+                                            &mt('Essay is [_1]% similar to an essay by [_2]',
+                                                $osim,
+                                                &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')').
+                                            '</span></h3>';
+                            } else {
+                                my %old_course_desc;
+                                if ($ocrsid ne '') {
+                                    if (ref($coursedesc_by_cid->{$ocrsid}) eq 'HASH') {
+                                        %old_course_desc = %{$coursedesc_by_cid->{$ocrsid}};
+                                    } else {
+                                        my $args;
+                                        if ($ocrsid ne $env{'request.course.id'}) {
+                                            $args = {'one_time' => 1};
+                                        }
+                                        %old_course_desc =
+                                            &Apache::lonnet::coursedescription($ocrsid,$args);
+                                        $coursedesc_by_cid->{$ocrsid} = \%old_course_desc;
+                                    }
+                                    $similar .=
+                                        '<h3><span class="LC_warning">'.
+                                        &mt('Essay is [_1]% similar to an essay by [_2] in course [_3] (course id [_4]:[_5])',
+                                            $osim,
+                                            &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')',
+                                            $old_course_desc{'description'},
+                                            $old_course_desc{'num'},
+                                            $old_course_desc{'domain'}).
+                                        '</span></h3>';
+                                } else {
+                                    $similar .=
+                                        '<h3><span class="LC_warning">'.
+                                        &mt('Essay is [_1]% similar to an essay by [_2] in an unknown course',
+                                            $osim,
+                                            &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')').
+                                        '</span></h3>';
+                                }
+                            }
+                            $similar .= '<blockquote><i>'.
+                                        &keywords_highlight($oessay).
+                                        '</i></blockquote><hr />';
+                        }
+                    }
+                }
+                my $order=&get_order($partid,$respid,$symb,$uname,$udom,
+                                     undef,$type,$trial,$rndseed);
+                if (($viewtype eq 'lastonly') ||
+                    ($viewtype eq 'datesub')  ||
+                    ($viewtype =~ /^(last|all)$/)) {
+                    my $display_part=&get_display_part($partid,$symb);
+                    $lastsubonly.='<div class="LC_grade_submission_part">'.
+                        '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
+                        ' <span class="LC_internal_info">'.
+                        '('.&mt('Response ID: [_1]',$respid).')'.
+                        '</span>&nbsp; &nbsp;';
+                    my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
+                    if (@$files) {
+                        if ($hide eq 'anon') {
+                            $lastsubonly.='<br />'.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
+                        } else {
+                            $lastsubonly.='<br /><br />'.'<b>'.&mt('Submitted Files:').'</b>'
+                                        .'<br /><span class="LC_warning">';
+                            if(@$files == 1) {
+                                $lastsubonly .= &mt('Like all files provided by users, this file may contain viruses!');
+                            } else {
+                                $lastsubonly .= &mt('Like all files provided by users, these files may contain viruses!');
+                            }
+                            $lastsubonly .= '</span>';
+                            foreach my $file (@$files) {
+                                &Apache::lonnet::allowuploaded('/adm/grades',$file);
+                                $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" alt="" /> '.$file.'</a>';
+                            }
+                        }
+                        $lastsubonly.='<br />';
+                    }
+                    if ($hide eq 'anon') {
+                        $lastsubonly.='<br /><b>'.&mt('Anonymous Survey').'</b>';
+                    } else {
+                        $lastsubonly.='<br /><b>'.&mt('Submitted Answer:').' </b>';
+                        if ($draft) {
+                            $lastsubonly.= ' <span class="LC_warning">'.&mt('Draft Copy').'</span>';
+                        }
+                        $subval =
+                            &cleanRecord($subval,$responsetype,$symb,$partid,
+                                         $respid,$record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
+                        if ($responsetype eq 'essay') {
+                            $subval =~ s{\n}{<br />}g;
+                        }
+                        $lastsubonly.=$subval."\n";
+                    }
+                    if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
+                    $lastsubonly.='</div>';
+                }
+            }
+        }
+        $lastsubonly.='</div>'."\n"; # End: LC_grade_submissions_body
+    }
+    return ($lastsubonly,$partinfo);
+}
+
 sub check_collaborators {
     my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
     my ($result,@col_fullnames);
@@ -2901,17 +2926,45 @@ sub check_collaborators {
 #--- Retrieve the last submission for all the parts
 sub get_last_submission {
     my ($returnhash,$is_tool)=@_;
-    my (@string,$timestamp,%lasthidden);
+    my (@string,$timestamp,$lastgradetime,$lastsubmittime);
     if ($$returnhash{'version'}) {
 	my %lasthash=();
-	my ($version);
+        my %prevsolved=();
+        my %solved=();
+	my $version;
 	for ($version=1;$version<=$$returnhash{'version'};$version++) {
+            my %handgraded = ();
 	    foreach my $key (sort(split(/\:/,
 					$$returnhash{$version.':keys'}))) {
 		$lasthash{$key}=$$returnhash{$version.':'.$key};
-		$timestamp = 
-		    &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
+                if ($key =~ /\.([^.]+)\.regrader$/) {
+                    $handgraded{$1} = 1;
+                } elsif ($key =~ /\.portfiles$/) {
+                    if (($$returnhash{$version.':'.$key} ne '') &&
+                        ($$returnhash{$version.':'.$key} !~ /\.\d+\.\w+$/)) {
+                        $lastsubmittime = $$returnhash{$version.':timestamp'};
+                    }
+                } elsif ($key =~ /\.submission$/) {
+                    if ($$returnhash{$version.':'.$key} ne '') {
+                        $lastsubmittime = $$returnhash{$version.':timestamp'};
+                    }
+                } elsif ($key =~ /\.([^.]+)\.solved$/) {
+                    $prevsolved{$1} = $solved{$1};
+                    $solved{$1} = $lasthash{$key};
+                }
+            }
+            foreach my $partid (keys(%handgraded)) {
+                if (($prevsolved{$partid} eq 'ungraded_attempted') &&
+                    (($solved{$partid} eq 'incorrect_by_override') ||
+                     ($solved{$partid} eq 'correct_by_override'))) {
+                    $lastgradetime = $$returnhash{$version.':timestamp'};
+                }
+                if ($solved{$partid} ne '') {
+                    $prevsolved{$partid} = $solved{$partid};
+                }
 	    }
+            $timestamp =
+                &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
 	}
         my (%typeparts,%randombytry);
         my $showsurv = 
@@ -2975,7 +3028,7 @@ sub get_last_submission {
 	$string[0] =
 	    '<span class="LC_warning">'.$msg.'</span>';
     }
-    return (\@string,\$timestamp);
+    return (\@string,$timestamp,$lastgradetime,$lastsubmittime);
 }
 
 #--- High light keywords, with style choosen by user.
@@ -3182,13 +3235,31 @@ sub processHandGrade {
     my $ntstu  = $env{'form.NTSTU'};
     my $cdom   = $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum   = $env{'course.'.$env{'request.course.id'}.'.num'};
+    my ($res_error,%queueable);
+    my ($partlist,$handgrade,$responseType,$numresp,$numessay) = &response_type($symb,\$res_error);
+    if ($res_error) {
+        $request->print(&navmap_errormsg());
+        return;
+    } else {
+        foreach my $part (@{$partlist}) {
+            if (ref($responseType->{$part}) eq 'HASH') {
+                foreach my $id (keys(%{$responseType->{$part}})) {
+                    if (($responseType->{$part}->{$id} eq 'essay') ||
+                        (lc($handgrade->{$part.'_'.$id}) eq 'yes')) {
+                        $queueable{$part} = 1;
+                        last;
+                    }
+                }
+            }
+        }
+    }
 
     if ($button eq 'Save & Next') {
 	my $ctr = 0;
 	while ($ctr < $ngrade) {
 	    my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
 	    my ($errorflag,$pts,$wgt,$numhidden) = 
-                &saveHandGrade($request,$symb,$uname,$udom,$ctr);
+                &saveHandGrade($request,$symb,$uname,$udom,$ctr,undef,undef,\%queueable);
 	    if ($errorflag eq 'no_score') {
 		$ctr++;
 		next;
@@ -3243,7 +3314,7 @@ sub processHandGrade {
 		    foreach my $collaborator (@collaborators) {
 			my ($errorflag,$pts,$wgt) = 
 			    &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
-					   $env{'form.unamedom'.$ctr},$part);
+					   $env{'form.unamedom'.$ctr},$part,\%queueable);
 			if ($errorflag eq 'not_allowed') {
 			    $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
 			    next;
@@ -3265,13 +3336,6 @@ sub processHandGrade {
 	}
     }
 
-    my $res_error;
-    my ($partlist,$handgrade,$responseType,$numresp,$numessay) = &response_type($symb,\$res_error);
-    if ($res_error) {
-        $request->print(&navmap_errormsg());
-        return;
-    }
-
     my %keyhash = ();
     if ($numessay) {
 	# Keywords sorted in alphabatical order
@@ -3423,7 +3487,7 @@ sub processHandGrade {
 
 #---- Save the score and award for each student, if changed
 sub saveHandGrade {
-    my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
+    my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part,$queueable) = @_;
     my @version_parts;
     my $usec = &Apache::lonnet::getsection($domain,$stuname,
 					   $env{'request.course.id'});
@@ -3535,7 +3599,7 @@ sub saveHandGrade {
 	&Apache::lonnet::cstore(\%newrecord,$symb,
 				$env{'request.course.id'},$domain,$stuname);
 	&check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
-				     $cdom,$cnum,$domain,$stuname);
+				     $cdom,$cnum,$domain,$stuname,$queueable);
     }
     if ($aggregateflag) {
         &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
@@ -3575,7 +3639,7 @@ sub makehidden {
 }
 
 sub check_and_remove_from_queue {
-    my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
+    my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname,$queueable) = @_;
     my @ungraded_parts;
     foreach my $part (@{$parts}) {
 	if (    $record->{   'resource.'.$part.'.awarded'} eq ''
@@ -3583,7 +3647,9 @@ sub check_and_remove_from_queue {
 	     && $newrecord->{'resource.'.$part.'.awarded'} eq ''
 	     && $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
 		) {
-	    push(@ungraded_parts, $part);
+            if ($queueable->{$part}) {
+	        push(@ungraded_parts, $part);
+            }
 	}
     }
     if ( !@ungraded_parts ) {
@@ -4476,6 +4542,7 @@ sub editgrades {
 	&Apache::loncommon::end_data_table_header_row();
     my @noupdate;
     my ($updateCtr,$noupdateCtr) = (1,1);
+    my ($got_types,%queueable);
     for ($i=0; $i<$env{'form.total'}; $i++) {
 	my $user = $env{'form.ctr'.$i};
 	my ($uname,$udom)=split(/:/,$user);
@@ -4575,14 +4642,33 @@ sub editgrades {
 					     $udom,$uname);
 		my $all_graded = 1;
 		my $none_graded = 1;
+                unless ($got_types) {
+                    my $error;
+                    my ($plist,$handgrd,$resptype) = &response_type($symb,\$error);
+                    unless ($error) {
+                        foreach my $part (@parts) {
+                            if (ref($resptype->{$part}) eq 'HASH') {
+                                foreach my $id (keys(%{$resptype->{$part}})) {
+                                    if (($resptype->{$part}->{$id} eq 'essay') ||
+                                        (lc($handgrd->{$part.'_'.$id}) eq 'yes')) {
+                                        $queueable{$part} = 1;
+                                        last;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    $got_types = 1;
+                }
 		foreach my $part (@parts) {
-		    if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
-			$all_graded = 0;
-		    } else {
-			$none_graded = 0;
+                    if ($queueable{$part}) {
+		        if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
+			    $all_graded = 0;
+		        } else {
+			    $none_graded = 0;
+		        }
 		    }
-		}
-
+                }
 		if ($all_graded || $none_graded) {
 		    &Apache::bridgetask::remove_from_queue('gradingqueue',
 							   $symb,$cdom,$cnum,
@@ -5696,6 +5782,7 @@ sub updateGradeByPage {
 	    my @displayPts=();
             my %aggregate = ();
             my $aggregateflag = 0;
+            my %queueable;
             if ($env{'form.HIDE'.$prob}) {
                 my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
                 my ($version,$parts) = split(/:/,$env{'form.HIDE'.$prob},2);
@@ -5705,7 +5792,20 @@ sub updateGradeByPage {
 	    foreach my $partid (@{$parts}) {
 		my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
 		my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
-
+                my @types = $curRes->responseType($partid);
+                if (grep(/^essay$/,@types)) {
+                    $queueable{$partid} = 1;
+                } else {
+                    my @ids = $curRes->responseIds($partid);
+                    for (my $i=0; $i < scalar(@ids); $i++) {
+                        my $hndgrd = &Apache::lonnet::EXT('resource.'.$partid.'_'.$ids[$i].
+                                                          '.handgrade',$symb);
+                        if (lc($hndgrd) eq 'yes') {
+                            $queueable{$partid} = 1;
+                            last;
+                        }
+                    }
+                }
 		my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
 		    $env{'form.WGT'.$question.'_'.$partid} : 1;
 		my $partial = $newpts/$wgt;
@@ -5771,7 +5871,7 @@ sub updateGradeByPage {
 						   $env{'request.course.id'},
 						   $udom,$uname);
 		&check_and_remove_from_queue($parts,\%record,undef,$symbx,
-					     $cdom,$cnum,$udom,$uname);
+					     $cdom,$cnum,$udom,$uname,\%queueable);
 	    }
 	    
             if ($aggregateflag) {
@@ -6683,9 +6783,12 @@ sub scantron_parse_scanline {
 }
 
 sub get_master_seq {
-    my ($resources,$master_seq,$symb_to_resource) = @_;
+    my ($resources,$master_seq,$symb_to_resource,$need_symb_in_map,$symb_for_examcode) = @_;
     return unless ((ref($resources) eq 'ARRAY') && (ref($master_seq) eq 'ARRAY') && 
                    (ref($symb_to_resource) eq 'HASH'));
+    if ($need_symb_in_map) {
+        return unless (ref($symb_for_examcode) eq 'HASH');
+    }
     my $resource_error;
     foreach my $resource (@{$resources}) {
         my $ressymb;
@@ -6693,6 +6796,14 @@ sub get_master_seq {
             $ressymb = $resource->symb();
             push(@{$master_seq},$ressymb);
             $symb_to_resource->{$ressymb} = $resource;
+            if ($need_symb_in_map) {
+                unless ($resource->is_map()) {
+                    my $map=(&Apache::lonnet::decode_symb($ressymb))[0];
+                    unless (exists($symb_for_examcode->{$map})) {
+                        $symb_for_examcode->{$map} = $ressymb;
+                    }
+                }
+            }
         } else {
             $resource_error = 1;
             last;
@@ -8704,6 +8815,17 @@ sub scantron_validate_doublebubble {
     if (ref($map)) {
         $randomorder = $map->randomorder();
         $randompick = $map->randompick();
+        unless ($randomorder || $randompick) {
+            foreach my $res ($navmap->retrieveResources($map,sub { $_[0]->is_map() },1,0,1)) {
+                if ($res->randomorder()) {
+                    $randomorder = 1;
+                }
+                if ($res->randompick()) {
+                    $randompick = 1;
+                }
+                last if ($randomorder || $randompick);
+            }
+        }
         if ($randomorder || $randompick) {
             $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
             if ($nav_error) {
@@ -8887,6 +9009,17 @@ sub scantron_validate_missingbubbles {
     if (ref($map)) {
         $randomorder = $map->randomorder();
         $randompick = $map->randompick();
+        unless ($randomorder || $randompick) {
+            foreach my $res ($navmap->retrieveResources($map,sub { $_[0]->is_map() },1,0,1)) {
+                if ($res->randomorder()) {
+                    $randomorder = 1;
+                }
+                if ($res->randompick()) {
+                    $randompick = 1;
+                }
+                last if ($randomorder || $randompick);
+            }
+        }
         if ($randomorder || $randompick) {
             $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
             if ($nav_error) {
@@ -9028,10 +9161,21 @@ sub scantron_process_students {
     }
     my $map=$navmap->getResourceByUrl($sequence);
     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
-        %grader_randomlists_by_symb);
+        %grader_randomlists_by_symb,%symb_for_examcode);
     if (ref($map)) {
         $randomorder = $map->randomorder();
         $randompick = $map->randompick();
+        unless ($randomorder || $randompick) {
+            foreach my $res ($navmap->retrieveResources($map,sub { $_[0]->is_map() },1,0,1)) {
+                if ($res->randomorder()) {
+                    $randomorder = 1;
+                }
+                if ($res->randompick()) {
+                    $randompick = 1;
+                }
+                last if ($randomorder || $randompick);
+            }
+        }
     } else {
         $r->print(&navmap_errormsg());
         return '';
@@ -9039,7 +9183,7 @@ sub scantron_process_students {
     my $nav_error;
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
     if ($randomorder || $randompick) {
-        $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+        $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource,1,\%symb_for_examcode);
         if ($nav_error) {
             $r->print(&navmap_errormsg());
             return '';
@@ -9194,11 +9338,16 @@ SCANTRONFORM
         }
 
         if (($scancode) && ($randomorder || $randompick)) {
-            my $parmresult =
-                &Apache::lonparmset::storeparm_by_symb($symb,
-                                                       '0_examcode',2,$scancode,
-                                                       'string_examcode',$uname,
-                                                       $udom);
+            foreach my $key (keys(%symb_for_examcode)) {
+                my $symb_in_map = $symb_for_examcode{$key};
+                if ($symb_in_map ne '') {
+                    my $parmresult =
+                        &Apache::lonparmset::storeparm_by_symb($symb_in_map,
+                                                               '0_examcode',2,$scancode,
+                                                               'string_examcode',$uname,
+                                                               $udom);
+                }
+            }
         }
 	$completedstudents{$uname}={'line'=>$line};
         if ($env{'form.verifyrecord'}) {
@@ -9513,7 +9662,7 @@ END
                             my @lines = &Apache::lonnet::get_scantronformat_file();
                             my $count = 0;
                             foreach my $line (@lines) {
-                                next if ($line =~ /^#/);
+                                next if (($line =~ /^\#/) || ($line eq ''));
                                 $singleline = $line;
                                 $count ++;
                             }
@@ -9707,7 +9856,7 @@ sub validate_uploaded_scantron_file {
         my %unique_formats;
         my @formatlines = &Apache::lonnet::get_scantronformat_file();
         foreach my $line (@formatlines) {
-            chomp($line);
+            next if (($line =~ /^\#/) || ($line eq ''));
             my @config = split(/:/,$line);
             my $idstart = $config[5];
             my $idlength = $config[6];
@@ -10077,6 +10226,17 @@ sub checkscantron_results {
     if (ref($map)) { 
         $randomorder=$map->randomorder();
         $randompick=$map->randompick();
+        unless ($randomorder || $randompick) {
+            foreach my $res ($navmap->retrieveResources($map,sub { $_[0]->is_map() },1,0,1)) {
+                if ($res->randomorder()) {
+                    $randomorder = 1;
+                }
+                if ($res->randompick()) {
+                    $randompick = 1;
+                }
+                last if ($randomorder || $randompick);
+            }
+        }
     }
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
     my $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
@@ -11383,7 +11543,9 @@ sub startpage {
         $args{'only_body'} = 1; 
         $r->print(&Apache::loncommon::start_page("Student's Version",$head_extra,\%args));
     } else {
-        unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
+        if ($env{'request.course.id'}) { 
+            unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
+        }
         $args{'bread_crumbs'} = $crumbs;
         $r->print(&Apache::loncommon::start_page('Grading',$head_extra,\%args));
         if ($env{'request.course.id'}) {
@@ -11403,6 +11565,77 @@ sub select_problem {
     $r->print('<input type="submit" value="'.&mt('Next').' &rarr;" /></form>');
 }
 
+#----- display problem, answer, and submissions for a single student (no grading)
+
+sub view_as_user {
+    my ($symb,$vuname,$vudom,$hasperm) = @_;
+    my $plainname = &Apache::loncommon::plainname($vuname,$vudom,'lastname');
+    my $displayname = &nameUserString('',$plainname,$vuname,$vudom);
+    my $output = &Apache::loncommon::get_student_view($symb,$vuname,$vudom,
+                                                      $env{'request.course.id'},
+                                                      undef,{'disable_submit' => 1}).
+                 "\n\n".
+                 '<div class="LC_grade_show_user">'.
+                 '<h2>'.$displayname.'</h2>'.
+                 "\n".
+                 &Apache::loncommon::track_student_link('View recent activity',
+                                                        $vuname,$vudom,'check').' '.
+                 "\n";
+    if (&Apache::lonnet::allowed('opa',$env{'request.course.id'}) ||
+        (($env{'request.course.sec'} ne '') &&
+         &Apache::lonnet::allowed('opa',$env{'request.course.id'}.'/'.$env{'request.course.sec'}))) {
+        $output .= &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),
+                                               $vuname,$vudom,$symb,'check');
+    }
+    $output .= "\n";
+    my $companswer = &Apache::loncommon::get_student_answers($symb,$vuname,$vudom,
+                                                             $env{'request.course.id'});
+    $companswer=~s|<form(.*?)>||g;
+    $companswer=~s|</form>||g;
+    $companswer=~s|name="submit"|name="would_have_been_submit"|g;
+    $output .= '<div class="LC_Box">'.
+               '<h3 class="LC_hcell">'.&mt('Correct answer for[_1]',$displayname).'</h3>'.
+               $companswer.
+               '</div>'."\n";
+    my $is_tool = ($symb =~ /ext\.tool$/);
+    my ($essayurl,%coursedesc_by_cid);
+    (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
+    my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$vudom,$vuname);
+    my $res_error;
+    my ($partlist,$handgrade,$responseType,$numresp,$numessay) =
+        &response_type($symb,\$res_error);
+    my $fullname;
+    my $collabinfo;
+    if ($numessay) {
+        unless ($hasperm) {
+            &init_perm();
+        }
+        ($collabinfo,$fullname)=
+            &check_collaborators($symb,$vuname,$vudom,\%record,$handgrade,0);
+        unless ($hasperm) {
+            &reset_perm();
+        }
+    }
+    my $checkIcon = '<img alt="'.&mt('Check Mark').
+                    '" src="'.$Apache::lonnet::perlvar{'lonIconsURL'}.
+                    '/check.gif" height="16" border="0" />';
+    my ($lastsubonly,$partinfo) =
+        &show_last_submission($vuname,$vudom,$symb,$essayurl,$responseType,'datesub',
+                              '',$fullname,\%record,\%coursedesc_by_cid);
+    $output .= '<div class="LC_Box">'.
+               '<h3 class="LC_hcell">'.&mt('Submissions').'</h3>'."\n".$collabinfo."\n";
+    if (($numresp > $numessay) & !$is_tool) {
+        $output .='<p class="LC_info">'.
+                  &mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon).
+                  "</p>\n";
+    }
+    $output .= $partinfo;
+    $output .= $lastsubonly;
+    $output .= &displaySubByDates($symb,\%record,$partlist,$responseType,$checkIcon,$vuname,$vudom);
+    $output .= '</div></div>'."\n";
+    return $output;
+}
+
 sub handler {
     my $request=$_[0];
     &reset_caches();
@@ -11829,8 +12062,8 @@ Side Effects: None.
          - missingbubble - array ref of the bubble lines that have missing
                            bubble errors
 
-   $randomorder - True if exam folder has randomorder set
-   $randompick  - True if exam folder has randompick set
+   $randomorder - True if exam folder (or a sub-folder) has randomorder set
+   $randompick  - True if exam folder (or a sub-folder) has randompick set
    $respnumlookup - Reference to HASH mapping question numbers in bubble lines
                      for current line to question number used for same question
                      in "Master Seqence" (as seen by Course Coordinator).