--- loncom/homework/grades.pm	2009/12/27 01:51:07	1.574.2.5
+++ loncom/homework/grades.pm	2019/01/31 17:44:18	1.757
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.574.2.5 2009/12/27 01:51:07 raeburn Exp $
+# $Id: grades.pm,v 1.757 2019/01/31 17:44:18 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -40,9 +40,13 @@ use Apache::lonhomework;
 use Apache::lonpickcode;
 use Apache::loncoursedata;
 use Apache::lonmsg();
-use Apache::Constants qw(:common);
+use Apache::Constants qw(:common :http);
 use Apache::lonlocal;
 use Apache::lonenc;
+use Apache::lonstathelpers;
+use Apache::lonquickgrades;
+use Apache::bridgetask();
+use Apache::lontexconvert();
 use String::Similarity;
 use LONCAPA;
 
@@ -51,6 +55,7 @@ use POSIX qw(floor);
 
 
 my %perm=();
+my %old_essays=();
 
 #  These variables are used to recover from ssi errors
 
@@ -96,14 +101,27 @@ sub ssi_print_error {
 
 #
 # --- Retrieve the parts from the metadata file.---
+# Returns an array of everything that the resources stores away
+#
+
 sub getpartlist {
-    my ($symb) = @_;
+    my ($symb,$errorref) = @_;
 
     my $navmap   = Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        if (ref($errorref)) { 
+            $$errorref = 'navmap';
+            return;
+        }
+    }
     my $res      = $navmap->getBySymb($symb);
     my $partlist = $res->parts();
     my $url      = $res->src();
-    my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
+    my $toolsymb;
+    if ($url =~ /ext\.tool$/) {
+        $toolsymb = $symb;
+    }
+    my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys',$toolsymb));
 
     my @stores;
     foreach my $part (@{ $partlist }) {
@@ -114,21 +132,6 @@ sub getpartlist {
     return @stores;
 }
 
-# --- Get the symbolic name of a problem and the url
-sub get_symb {
-    my ($request,$silent) = @_;
-    (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
-    my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
-    if ($symb eq '') { 
-	if (!$silent) {
-	    $request->print("Unable to handle ambiguous references:$url:.");
-	    return ();
-	}
-    }
-    &Apache::lonenc::check_decrypt(\$symb);
-    return ($symb);
-}
-
 #--- Format fullname, username:domain if different for display
 #--- Use anywhere where the student names are listed
 sub nameUserString {
@@ -143,11 +146,22 @@ sub nameUserString {
 
 #--- Get the partlist and the response type for a given problem. ---
 #--- Indicate if a response type is coded handgraded or not. ---
+#--- Sets response_error pointer to "1" if navmaps object broken ---
 sub response_type {
-    my ($symb) = shift;
+    my ($symb,$response_error) = @_;
 
     my $navmap = Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        if (ref($response_error)) {
+            $$response_error = 1;
+        }
+        return;
+    }
     my $res = $navmap->getBySymb($symb);
+    unless (ref($res)) {
+        $$response_error = 1;
+        return;
+    }
     my $partlist = $res->parts();
     my %vPart = 
 	map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
@@ -183,53 +197,18 @@ sub get_display_part {
     my ($partID,$symb)=@_;
     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
     if (defined($display) and $display ne '') {
-	$display.= " (<span class=\"LC_internal_info\">id $partID</span>)";
+        $display.= ' (<span class="LC_internal_info">'
+                  .&mt('Part ID: [_1]',$partID).'</span>)';
     } else {
 	$display=$partID;
     }
     return $display;
 }
 
-#--- Show resource title
-#--- and parts and response type
-sub showResourceInfo {
-    my ($symb,$probTitle,$checkboxes) = @_;
-    my $col=3;
-    if ($checkboxes) { $col=4; }
-    my $result = '<h3>'.&mt('Current Resource').': '.$probTitle.'</h3>'."\n";
-    $result .='<table border="0">';
-    my ($partlist,$handgrade,$responseType) = &response_type($symb);
-    my %resptype = ();
-    my $hdgrade='no';
-    my %partsseen;
-    foreach my $partID (sort(keys(%$responseType))) {
-	foreach my $resID (sort(keys(%{ $responseType->{$partID} }))) {
-	    my $handgrade=$$handgrade{$partID.'_'.$resID};
-	    my $responsetype = $responseType->{$partID}->{$resID};
-	    $hdgrade = $handgrade if ($handgrade eq 'yes');
-	    $result.='<tr>';
-	    if ($checkboxes) {
-		if (exists($partsseen{$partID})) {
-		    $result.="<td>&nbsp;</td>";
-		} else {
-		    $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='checked' /></td>";
-		}
-		$partsseen{$partID}=1;
-	    }
-	    my $display_part=&get_display_part($partID,$symb);
-	    $result.='<td><b>'.&mt('Part').': </b>'.$display_part.
-                ' <span class="LC_internal_info">'.$resID.'</span></td>'.
-		'<td><b>'.&mt('Type').': </b>'.$responsetype.'</td></tr>';
-#	    '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>';
-	}
-    }
-    $result.='</table>'."\n";
-    return $result,$responseType,$hdgrade,$partlist,$handgrade;
-}
-
 sub reset_caches {
     &reset_analyze_cache();
     &reset_perm();
+    &reset_old_essays();
 }
 
 {
@@ -242,8 +221,13 @@ sub reset_caches {
     }
 
     sub get_analyze {
-	my ($symb,$uname,$udom,$no_increment,$add_to_hash)=@_;
+	my ($symb,$uname,$udom,$no_increment,$add_to_hash,$type,$trial,$rndseed,$bubbles_per_row)=@_;
 	my $key = "$symb\0$uname\0$udom";
+        if ($type eq 'randomizetry') {
+            if ($trial ne '') {
+                $key .= "\0".$trial;
+            }
+        }
 	if (exists($analyze_cache{$key})) {
             my $getupdate = 0;
             if (ref($add_to_hash) eq 'HASH') {
@@ -271,9 +255,18 @@ sub reset_caches {
                     'grade_courseid'    =>  $env{'request.course.id'},
                     'grade_username'    => $uname,
                     'grade_noincrement' => $no_increment);
+        if ($bubbles_per_row ne '') {
+            $form{'bubbles_per_row'} = $bubbles_per_row;
+        }
+        if ($type eq 'randomizetry') {
+            $form{'grade_questiontype'} = $type;
+            if ($rndseed ne '') {
+                $form{'grade_rndseed'} = $rndseed;
+            }
+        }
         if (ref($add_to_hash)) {
             %form = (%form,%{$add_to_hash});
-        } 
+        }
 	my $subresult=&ssi_with_retries($url, $ssi_retries,%form);
 	(undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
 	my %analyze=&Apache::lonnet::str2hash($subresult);
@@ -286,15 +279,15 @@ sub reset_caches {
     }
 
     sub get_order {
-	my ($partid,$respid,$symb,$uname,$udom,$no_increment)=@_;
-	my $analyze = &get_analyze($symb,$uname,$udom,$no_increment);
+	my ($partid,$respid,$symb,$uname,$udom,$no_increment,$type,$trial,$rndseed)=@_;
+	my $analyze = &get_analyze($symb,$uname,$udom,$no_increment,undef,$type,$trial,$rndseed);
 	return $analyze->{"$partid.$respid.shown"};
     }
 
     sub get_radiobutton_correct_foil {
-	my ($partid,$respid,$symb,$uname,$udom)=@_;
-	my $analyze = &get_analyze($symb,$uname,$udom);
-        my $foils = &get_order($partid,$respid,$symb,$uname,$udom);
+	my ($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed)=@_;
+	my $analyze = &get_analyze($symb,$uname,$udom,undef,undef,$type,$trial,$rndseed);
+        my $foils = &get_order($partid,$respid,$symb,$uname,$udom,undef,$type,$trial,$rndseed);
         if (ref($foils) eq 'ARRAY') {
 	    foreach my $foil (@{$foils}) {
 	        if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
@@ -305,7 +298,7 @@ sub reset_caches {
     }
 
     sub scantron_partids_tograde {
-        my ($resource,$cid,$uname,$udom,$check_for_randomlist) = @_;
+        my ($resource,$cid,$uname,$udom,$check_for_randomlist,$bubbles_per_row,$scancode) = @_;
         my (%analysis,@parts);
         if (ref($resource)) {
             my $symb = $resource->symb();
@@ -313,7 +306,16 @@ sub reset_caches {
             if ($check_for_randomlist) {
                 $add_to_form = { 'check_parts_withrandomlist' => 1,};
             }
-            my $analyze = &get_analyze($symb,$uname,$udom,undef,$add_to_form);
+            if ($scancode) {
+                if (ref($add_to_form) eq 'HASH') {
+                    $add_to_form->{'code_for_randomlist'} = $scancode;
+                } else {
+                    $add_to_form = { 'code_for_randomlist' => $scancode,};
+                }
+            }
+            my $analyze = 
+                &get_analyze($symb,$uname,$udom,undef,$add_to_form,
+                             undef,undef,undef,$bubbles_per_row);
             if (ref($analyze) eq 'HASH') {
                 %analysis = %{$analyze};
             }
@@ -336,10 +338,12 @@ sub reset_caches {
 #        response types only.
 sub cleanRecord {
     my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
-	$uname,$udom) = @_;
+	$uname,$udom,$type,$trial,$rndseed) = @_;
     my $grayFont = '<span class="LC_internal_info">';
     if ($response =~ /^(option|rank)$/) {
 	my %answer=&Apache::lonnet::str2hash($answer);
+        my @answer = %answer;
+        %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
 	my ($toprow,$bottomrow);
 	foreach my $foil (@$order) {
@@ -353,9 +357,11 @@ sub cleanRecord {
 	return '<blockquote><table border="1">'.
 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
-	    $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
+	    $bottomrow.'</tr></table></blockquote>';
     } elsif ($response eq 'match') {
 	my %answer=&Apache::lonnet::str2hash($answer);
+        my @answer = %answer;
+        %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
 	my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
 	my ($toprow,$middlerow,$bottomrow);
@@ -375,12 +381,14 @@ sub cleanRecord {
 	    '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
 	    $middlerow.'</tr>'.
 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
-	    $bottomrow.'</tr>'.'</table></blockquote>';
+	    $bottomrow.'</tr></table></blockquote>';
     } elsif ($response eq 'radiobutton') {
 	my %answer=&Apache::lonnet::str2hash($answer);
+        my @answer = %answer;
+        %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
 	my ($toprow,$bottomrow);
 	my $correct = 
-	    &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);
+	    &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed);
 	foreach my $foil (@$order) {
 	    if (exists($answer{$foil})) {
 		if ($foil eq $correct) {
@@ -396,7 +404,7 @@ sub cleanRecord {
 	return '<blockquote><table border="1">'.
 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
-	    $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
+	    $bottomrow.'</tr></table></blockquote>';
     } elsif ($response eq 'essay') {
 	if (! exists ($env{'form.'.$symb})) {
 	    my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
@@ -410,10 +418,12 @@ sub cleanRecord {
 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
 	    $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
 	}
-	$answer =~ s-\n-<br />-g;
+        $answer = &Apache::lontexconvert::msgtexconverted($answer);
 	return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
+
     } elsif ( $response eq 'organic') {
-	my $result='Smile representation: "<tt>'.$answer.'</tt>"';
+        my $result=&mt('Smile representation: [_1]',
+                           '"<tt>'.&HTML::Entities::encode($answer, '"<>&').'</tt>"');
 	my $jme=$record->{$version."resource.$partid.$respid.molecule"};
 	$result.=&Apache::chemresponse::jme_img($jme,$answer,400);
 	return $result;
@@ -447,19 +457,20 @@ sub cleanRecord {
 	    $result.='</ul>';
 	    return $result;
 	}
-    } elsif ( $response =~ m/(?:numerical|formula)/) {
+    } elsif ( $response =~ m/(?:numerical|formula|custom)/) {
+        # Respect multiple input fields, see Bug #5409
 	$answer = 
 	    &Apache::loncommon::format_previous_attempt_value('submission',
 							      $answer);
+	return $answer;
     }
-    return $answer;
+    return &HTML::Entities::encode($answer, '"<>&');
 }
 
 #-- A couple of common js functions
 sub commonJSfunctions {
     my $request = shift;
-    $request->print(<<COMMONJSFUNCTIONS);
-<script type="text/javascript" language="javascript">
+    $request->print(&Apache::lonhtmlcommon::scripttag(<<COMMONJSFUNCTIONS));
     function radioSelection(radioButton) {
 	var selection=null;
 	if (radioButton.length > 1) {
@@ -487,14 +498,13 @@ sub commonJSfunctions {
 	    return selectOne.value;
 	}
     }
-</script>
 COMMONJSFUNCTIONS
 }
 
 #--- Dumps the class list with usernames,list of sections,
 #--- section, ids and fullnames for each user.
 sub getclasslist {
-    my ($getsec,$filterlist,$getgroup) = @_;
+    my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus) = @_;
     my @getsec;
     my @getgroup;
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
@@ -522,6 +532,13 @@ sub getclasslist {
     #
     my %sections;
     my %fullnames;
+    my ($cdom,$cnum,$partlist);
+    if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
+        $cdom = $env{"course.$env{'request.course.id'}.domain"};
+        $cnum = $env{"course.$env{'request.course.id'}.num"};
+        my $res_error;
+        ($partlist,my $handgrade,my $responseType) = &response_type($symb,\$res_error);
+    }
     foreach my $student (keys(%$classlist)) {
         my $end      = 
             $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
@@ -538,7 +555,7 @@ sub getclasslist {
         my $group   = 
             $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
 	# filter students according to status selected
-	if ($filterlist && (!($stu_status =~ /Any/))) {
+	if ($filterbyaccstatus && (!($stu_status =~ /Any/))) {
 	    if (!($stu_status =~ $status)) {
 		delete($classlist->{$student});
 		next;
@@ -555,13 +572,58 @@ sub getclasslist {
     	            } 
 	        }
     	        if (($grp eq 'none') && !$group) {
-        	        $exclude = 0;
+        	    $exclude = 0;
         	}
 	    }
 	    if ($exclude) {
 	        delete($classlist->{$student});
+		next;
 	    }
 	}
+        if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
+            my $udom =
+                $classlist->{$student}->[&Apache::loncoursedata::CL_SDOM()];
+            my $uname =
+                $classlist->{$student}->[&Apache::loncoursedata::CL_SNAME()];
+            if (($symb ne '') && ($udom ne '') && ($uname ne '')) {
+                if ($submitonly eq 'queued') {
+                    my %queue_status =
+                        &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
+                                                                $udom,$uname);
+                    if (!defined($queue_status{'gradingqueue'})) {
+                        delete($classlist->{$student});
+                        next;
+                    }
+                } else {
+                    my (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
+                    my $submitted = 0;
+                    my $graded = 0;
+                    my $incorrect = 0;
+                    foreach (keys(%status)) {
+                        $submitted = 1 if ($status{$_} ne 'nothing');
+                        $graded = 1 if ($status{$_} =~ /^ungraded/);
+                        $incorrect = 1 if ($status{$_} =~ /^incorrect/);
+
+                        my ($foo,$partid,$foo1) = split(/\./,$_);
+                        if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
+                            $submitted = 0;
+                        }
+                    }
+                    if (!$submitted && ($submitonly eq 'yes' ||
+                                        $submitonly eq 'incorrect' ||
+                                        $submitonly eq 'graded')) {
+                        delete($classlist->{$student});
+                        next;
+                    } elsif (!$graded && ($submitonly eq 'graded')) {
+                        delete($classlist->{$student});
+                        next;
+                    } elsif (!$incorrect && $submitonly eq 'incorrect') {
+                        delete($classlist->{$student});
+                        next;
+                    }
+                }
+            }
+        }
 	$section = ($section ne '' ? $section : 'none');
 	if (&canview($section)) {
 	    if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
@@ -576,7 +638,6 @@ sub getclasslist {
 	    delete($classlist->{$student});
 	}
     }
-    my %seen = ();
     my @sections = sort(keys(%sections));
     return ($classlist,\@sections,\%fullnames);
 }
@@ -642,17 +703,15 @@ sub student_gradeStatus {
 sub jscriptNform {
     my ($symb) = @_;
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
-    my $jscript='<script type="text/javascript" language="javascript">'."\n".
+    my $jscript= &Apache::lonhtmlcommon::scripttag(
 	'    function viewOneStudent(user,domain) {'."\n".
 	'	document.onestudent.student.value = user;'."\n".
 	'	document.onestudent.userdom.value = domain;'."\n".
 	'	document.onestudent.submit();'."\n".
 	'    }'."\n".
-	'</script>'."\n";
+	"\n");
     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
-	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
-	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
 	'<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
 	'<input type="hidden" name="command" value="submission" />'."\n".
 	'<input type="hidden" name="student" value="" />'."\n".
@@ -697,7 +756,11 @@ sub compute_points {
 #
 
 sub most_similar {
-    my ($uname,$udom,$uessay,$old_essays)=@_;
+    my ($uname,$udom,$symb,$uessay)=@_;
+
+    unless ($symb) { return ''; }
+
+    unless (ref($old_essays{$symb}) eq 'HASH') { return ''; }
 
 # ignore spaces and punctuation
 
@@ -705,7 +768,7 @@ sub most_similar {
 
 # ignore empty submissions (occuring when only files are sent)
 
-    unless ($uessay=~/\w+/) { return ''; }
+    unless ($uessay=~/\w+/s) { return ''; }
 
 # these will be returned. Do not care if not at least 50 percent similar
     my $limit=0.6;
@@ -714,11 +777,11 @@ sub most_similar {
     my $scrsid='';
     my $sessay='';
 # go through all essays ...
-    foreach my $tkey (keys(%$old_essays)) {
+    foreach my $tkey (keys(%{$old_essays{$symb}})) {
 	my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
 # ... except the same student
         next if (($tname eq $uname) && ($tdom eq $udom));
-	my $tessay=$old_essays->{$tkey};
+	my $tessay=$old_essays{$symb}{$tkey};
 	$tessay=~s/\W+/ /gs;
 # String similarity gives up if not even limit
 	my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
@@ -728,7 +791,7 @@ sub most_similar {
 	    $sname=$tname;
 	    $sdom=$tdom;
 	    $scrsid=$tcrsid;
-	    $sessay=$old_essays->{$tkey};
+	    $sessay=$old_essays{$symb}{$tkey};
 	}
     }
     if ($limit>0.6) {
@@ -742,22 +805,31 @@ sub most_similar {
 
 #------------------------------------ Receipt Verification Routines
 #
+
+sub initialverifyreceipt {
+   my ($request,$symb) = @_;
+   &commonJSfunctions($request);
+   return '<form name="gradingMenu" action=""><input type="submit" value="'.&mt('Verify Receipt Number.').'" />'.
+        &Apache::lonnet::recprefix($env{'request.course.id'}).
+        '-<input type="text" name="receipt" size="4" />'.
+        '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
+        '<input type="hidden" name="command" value="verify" />'.
+        "</form>\n";
+}
+
 #--- Check whether a receipt number is valid.---
 sub verifyreceipt {
-    my $request  = shift;
+    my ($request,$symb)  = @_;
 
     my $courseid = $env{'request.course.id'};
     my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
 	$env{'form.receipt'};
     $receipt     =~ s/[^\-\d]//g;
-    my ($symb)   = &get_symb($request);
 
     my $title.=
 	'<h3><span class="LC_info">'.
-	&mt('Verifying  Receipt No. [_1]',$receipt).
-	'</span></h3>'."\n".
-	'<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}).
-	'</h4>'."\n";
+	&mt('Verifying Receipt Number [_1]',$receipt).
+	'</span></h3>'."\n";
 
     my ($string,$contents,$matches) = ('','',0);
     my (undef,undef,$fullname) = &getclasslist('all','0');
@@ -766,7 +838,13 @@ sub verifyreceipt {
     if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
 	$env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
     my $parts=['0'];
-    if ($receiptparts) { ($parts)=&response_type($symb); }
+    if ($receiptparts) {
+        my $res_error; 
+        ($parts)=&response_type($symb,\$res_error);
+        if ($res_error) {
+            return &navmap_errormsg();
+        } 
+    }
     
     my $header = 
 	&Apache::loncommon::start_data_table().
@@ -808,17 +886,20 @@ sub verifyreceipt {
 	}
     }
     if ($matches == 0) {
-	$string = $title.&mt('No match found for the above receipt.');
+        $string = $title
+                 .'<p class="LC_warning">'
+                 .&mt('No match found for the above receipt number.')
+                 .'</p>';
     } else {
 	$string = &jscriptNform($symb).$title.
 	    '<p>'.
-	    &mt('The above receipt matches the following [numerate,_1,student].',$matches).
+	    &mt('The above receipt number matches the following [quant,_1,student].',$matches).
 	    '</p>'.
 	    $header.
 	    $contents.
 	    &Apache::loncommon::end_data_table()."\n";
     }
-    return $string.&show_grading_menu_form($symb);
+    return $string;
 }
 
 #--- This is called by a number of programs.
@@ -826,30 +907,27 @@ sub verifyreceipt {
 #--- Also called directly when one clicks on the subm button 
 #    on the problem page.
 sub listStudents {
-    my ($request) = shift;
+    my ($request,$symb,$submitonly) = @_;
 
-    my ($symb) = &get_symb($request);
+    my $is_tool   = ($symb =~ /ext\.tool$/);
     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
     my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
-    my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
-    my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
-    $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
-	&Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
-
-    my $result='<h3><span class="LC_info">&nbsp;'
-	.&mt("$viewgrade Submissions for a Student or a Group of Students")
-	.'</span></h3>';
+    unless ($submitonly) {
+       $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
+    }
 
-    my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
+    my $result='';
+    my $res_error;
+    my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
 
-    my %lt = &Apache::lonlocal::texthash (
+    my %js_lt = &Apache::lonlocal::texthash (
 		'multiple' => 'Please select a student or group of students before clicking on the Next button.',
 		'single'   => 'Please select the student before clicking on the Next button.',
 	     );
-    $request->print(<<LISTJAVASCRIPT);
-<script type="text/javascript" language="javascript">
+    &js_escape(\%js_lt);
+    $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
     function checkSelect(checkBox) {
 	var ctr=0;
 	var sense="";
@@ -859,12 +937,12 @@ sub listStudents {
 		    ctr++;
 		}
 	    }
-	    sense = '$lt{'multiple'}';
+	    sense = '$js_lt{'multiple'}';
 	} else {
 	    if (checkBox.checked) {
 		ctr = 1;
 	    }
-	    sense = '$lt{'single'}';
+	    sense = '$js_lt{'single'}';
 	}
 	if (ctr == 0) {
 	    alert(sense);
@@ -878,46 +956,75 @@ sub listStudents {
 	formname.command.value = 'submission';
 	formname.submit();
     }
-</script>
 LISTJAVASCRIPT
 
     &commonJSfunctions($request);
     $request->print($result);
 
-    my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';
-    my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';
     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
-	"\n".$table;
+	"\n";
 	
     $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
-    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
-                  .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
-                  .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
-                  .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
-                  .&Apache::lonhtmlcommon::row_closure();
-    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
-                  .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
-                  .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
-                  .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
-                  .&Apache::lonhtmlcommon::row_closure();
+    unless ($is_tool) {
+        $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
+                      .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
+                      .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
+                      .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
+                      .&Apache::lonhtmlcommon::row_closure();
+        $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
+                      .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
+                      .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
+                      .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
+                      .&Apache::lonhtmlcommon::row_closure();
+    }
 
     my $submission_options;
-    if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
-	$submission_options.=
-	    '<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> '.&mt('essay part only').' </label>'."\n";
-    }
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
     $env{'form.Status'} = $saveStatus;
+    my %optiontext;
+    if ($is_tool) {
+        %optiontext = &Apache::lonlocal::texthash (
+                          lastonly => 'last transaction',
+                          last     => 'last transaction with details',
+                          datesub  => 'all transactions',
+                          all      => 'all transactions with details',
+                      );
+    } else {
+        %optiontext = &Apache::lonlocal::texthash (
+                          lastonly => 'last submission',
+                          last     => 'last submission with details',
+                          datesub  => 'all submissions',
+                          all      => 'all submissions with details',
+                      );
+    }
     $submission_options.=
-	'<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> '.&mt('last submission only').' </label>'."\n".
-	'<label><input type="radio" name="lastSub" value="last" /> '.&mt('last submission &amp; parts info').' </label>'."\n".
-	'<label><input type="radio" name="lastSub" value="datesub" /> '.&mt('by dates and submissions').' </label>'."\n".
-	'<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').'</label>';
-    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions'))
+        '<span class="LC_nobreak">'.
+        '<label><input type="radio" name="lastSub" value="lastonly" /> '.
+        $optiontext{'lastonly'}.' </label></span>'."\n".
+        '<span class="LC_nobreak">'.
+        '<label><input type="radio" name="lastSub" value="last" /> '.
+        $optiontext{'last'}.' </label></span>'."\n".
+        '<span class="LC_nobreak">'.
+        '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.
+        $optiontext{'datesub'}.'</label></span>'."\n".
+        '<span class="LC_nobreak">'.
+        '<label><input type="radio" name="lastSub" value="all" /> '.
+        $optiontext{'all'}.'</label></span>';
+    my $viewtitle;
+    if ($is_tool) {
+        $viewtitle = &mt('View Transactions');
+    } else {
+        $viewtitle = &mt('View Submissions');
+    }
+    $gradeTable .= &Apache::lonhtmlcommon::row_title($viewtitle)
                   .$submission_options
                   .&Apache::lonhtmlcommon::row_closure();
 
+    my $closure;
+    if (($is_tool) && (exists($env{'form.Status'}))) {
+        $closure = 1;
+    }
     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments'))
                   .'<select name="increment">'
                   .'<option value="1">'.&mt('Whole Points').'</option>'
@@ -925,42 +1032,49 @@ LISTJAVASCRIPT
                   .'<option value=".25">'.&mt('Quarter Points').'</option>'
                   .'<option value=".1">'.&mt('Tenths of a Point').'</option>'
                   .'</select>'
-                  .&Apache::lonhtmlcommon::row_closure();
+                  .&Apache::lonhtmlcommon::row_closure($closure);
 
     $gradeTable .= 
         &build_section_inputs().
 	'<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
-	'<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
-	'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
-	'<input type="hidden" name="saveState"   value="'.$env{'form.saveState'}.'" />'."\n".
-	'<input type="hidden" name="probTitle"   value="'.$env{'form.probTitle'}.'" />'."\n".
 	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
 
-    if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
+    if (exists($env{'form.Status'})) {
 	$gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
     } else {
+        if ($is_tool) {
+            $closure = 1;
+        }
         $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status'))
                       .&Apache::lonhtmlcommon::StatusOptions(
                            $saveStatus,undef,1,'javascript:reLoadList(this.form);')
-                      .&Apache::lonhtmlcommon::row_closure();
+                      .&Apache::lonhtmlcommon::row_closure($closure);
     }
 
-    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
-                  .'<input type="checkbox" name="checkPlag" checked="checked" />'
-                  .&Apache::lonhtmlcommon::row_closure(1)
-                  .&Apache::lonhtmlcommon::end_pick_box();
-
+    unless ($is_tool) {
+        $closure = 1;
+        $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
+                      .'<input type="checkbox" name="checkPlag" checked="checked" />'
+                      .&Apache::lonhtmlcommon::row_closure($closure);
+    }
+    $gradeTable .= &Apache::lonhtmlcommon::end_pick_box();
+    my $regrademsg;
+    if ($is_tool) {
+        $regrademsg =&mt("To view/grade/regrade, click on the check box(es) next to the student's name(s). Then click on the Next button.");
+    } else {
+        $regrademsg = &mt("To view/grade/regrade a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.");
+    }
     $gradeTable .= '<p>'
-                  .&mt('To '.lc($viewgrade)." a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.")."\n"
+                  .$regrademsg."\n"
                   .'<input type="hidden" name="command" value="processGroup" />'
                   .'</p>';
 
 # checkall buttons
     $gradeTable.=&check_script('gradesub', 'stuinfo');
     $gradeTable.='<input type="button" '."\n".
-	'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
-	'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
+        'onclick="javascript:checkSelect(this.form.stuinfo);" '."\n".
+        'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
     $gradeTable.=&check_buttons();
     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
     $gradeTable.= &Apache::loncommon::start_data_table().
@@ -969,9 +1083,7 @@ LISTJAVASCRIPT
     while ($loop < 2) {
 	$gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
 	    '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
-	if ($env{'form.showgrading'} eq 'yes' 
-	    && $submitonly ne 'queued'
-	    && $submitonly ne 'all') {
+	if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
 	    foreach my $part (sort(@$partlist)) {
 		my $display_part=
 		    &get_display_part((split(/_/,$part))[0],$symb);
@@ -1007,9 +1119,7 @@ LISTJAVASCRIPT
 	    $status{'gradingqueue'} = $queue_status{'gradingqueue'};
 	}
 
-	if ($env{'form.showgrading'} eq 'yes' 
-	    && $submitonly ne 'queued'
-	    && $submitonly ne 'all') {
+	if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
 	    (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
 	    my $submitted = 0;
 	    my $graded = 0;
@@ -1050,7 +1160,7 @@ LISTJAVASCRIPT
 	       &nameUserString(undef,$$fullname{$student},$uname,$udom).
 	       '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
 
-	    if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
+	    if ($submitonly ne 'all') {
 		foreach (sort(keys(%status))) {
 		    next if ($_ =~ /^resource.*?submitted_by$/);
 		    $gradeTable.='<td align="center">&nbsp;'.&mt($status{$_}).'&nbsp;</td>'."\n";
@@ -1064,9 +1174,7 @@ LISTJAVASCRIPT
     }
     if ($ctr%2 ==1) {
 	$gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
-	    if ($env{'form.showgrading'} eq 'yes' 
-		&& $submitonly ne 'queued'
-		&& $submitonly ne 'all') {
+	    if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
 		foreach (@$partlist) {
 		    $gradeTable.='<td>&nbsp;</td>';
 		}
@@ -1077,9 +1185,9 @@ LISTJAVASCRIPT
     }
 
     $gradeTable.=&Apache::loncommon::end_data_table()."\n".
-	'<input type="button" '.
-	'onClick="javascript:checkSelect(this.form.stuinfo);" '.
-	'value="'.&mt('Next').' &rarr;" /></form>'."\n";
+        '<input type="button" '.
+        'onclick="javascript:checkSelect(this.form.stuinfo);" '.
+        'value="'.&mt('Next').' &rarr;" /></form>'."\n";
     if ($ctr == 0) {
 	my $num_students=(scalar(keys(%$fullname)));
 	if ($num_students eq 0) {
@@ -1090,14 +1198,13 @@ LISTJAVASCRIPT
 	    if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
 	    if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
 	    $gradeTable='<br />&nbsp;<span class="LC_warning">'.
-		&mt('No '.$submissions.' found for this resource for any students. ([_1] students checked for '.$submissions.')',
+		&mt('No '.$submissions.' found for this resource for any students. ([quant,_1,student] checked for '.$submissions.')',
 		    $num_students).
 		'</span><br />';
 	}
     } elsif ($ctr == 1) {
 	$gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/;
     }
-    $gradeTable.=&show_grading_menu_form($symb);
     $request->print($gradeTable);
     return '';
 }
@@ -1106,7 +1213,7 @@ LISTJAVASCRIPT
 
 sub check_script {
     my ($form, $type)=@_;
-    my $chkallscript='<script type="text/javascript">
+    my $chkallscript= &Apache::lonhtmlcommon::scripttag('
     function checkall() {
         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
             ele = document.forms.'.$form.'.elements[i];
@@ -1137,7 +1244,7 @@ sub check_script {
         }
     }
 
-</script>'."\n";
+'."\n");
     return $chkallscript;
 }
 
@@ -1151,7 +1258,7 @@ sub check_buttons {
 
 #     Displays the submissions for one student or a group of students
 sub processGroup {
-    my ($request)  = shift;
+    my ($request,$symb)  = @_;
     my $ctr        = 0;
     my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
     my $total      = scalar(@stuchecked)-1;
@@ -1161,7 +1268,7 @@ sub processGroup {
 	$env{'form.student'}        = $uname;
 	$env{'form.userdom'}        = $udom;
 	$env{'form.fullname'}       = $fullname;
-	&submission($request,$ctr,$total);
+	&submission($request,$ctr,$total,$symb);
 	$ctr++;
     }
     return '';
@@ -1175,9 +1282,9 @@ sub processGroup {
 #--- Javascript to handle the submission page functionality ---
 sub sub_page_js {
     my $request = shift;
-	    my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
-    $request->print(<<SUBJAVASCRIPT);
-<script type="text/javascript" language="javascript">
+    my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
+    &js_escape(\$alertmsg);
+    $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
     function updateRadio(formname,id,weight) {
 	var gradeBox = formname["GD_BOX"+id];
 	var radioButton = formname["RADVAL"+id];
@@ -1294,17 +1401,8 @@ sub sub_page_js {
 			    }
 			}
 		    }
-		    
 		}
 	    }
-	    
-	}
-	if (val == "Grade Student") {
-	    formname.showgrading.value = "yes";
-	    if (formname.Status.value == "") {
-		formname.Status.value = "Active";
-	    }
-	    formname.studentNo.value = total;
 	}
 	formname.submit();
     }
@@ -1345,7 +1443,6 @@ sub sub_page_js {
 
 	formname.submit();
     }
-</script>
 SUBJAVASCRIPT
 }
 
@@ -1355,8 +1452,8 @@ sub sub_page_kw_js {
     my $iconpath = $request->dir_config('lonIconsURL');
     &commonJSfunctions($request);
 
-    my $inner_js_msg_central=<<INNERJS;
-    <script text="text/javascript">
+    my $inner_js_msg_central= (<<INNERJS);
+<script type="text/javascript">
     function checkInput() {
       opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
       var nmsg   = opener.document.SCORE.savemsgN.value;
@@ -1393,11 +1490,11 @@ sub sub_page_kw_js {
       self.close()
 
     }
-    </script>
+</script>
 INNERJS
 
-    my $inner_js_highlight_central=<<INNERJS;
- <script type="text/javascript">
+    my $inner_js_highlight_central= (<<INNERJS);
+<script type="text/javascript">
     function updateChoice(flag) {
       opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
       opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
@@ -1431,13 +1528,42 @@ INNERJS
 
     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
     $docopen=~s/^document\.//;
-    my $alertmsg = &mt('Please select a word or group of words from document and then click this link.');
-    $request->print(<<SUBJAVASCRIPT);
-<script type="text/javascript" language="javascript">
+    my %js_lt = &Apache::lonlocal::texthash(
+                keyw => 'Keywords list, separated by a space. Add/delete to list if desired.',
+                plse => 'Please select a word or group of words from document and then click this link.',
+                adds => 'Add selection to keyword list? Edit if desired.',
+                col1 => 'red',
+                col2 => 'green',
+                col3 => 'blue',
+                siz1 => 'normal',
+                siz2 => '+1',
+                siz3 => '+2',
+                sty1 => 'normal',
+                sty2 => 'italic',
+                sty3 => 'bold',
+             );
+    my %html_js_lt = &Apache::lonlocal::texthash(
+                comp => 'Compose Message for: ',
+                incl => 'Include',
+                type => 'Type',
+                subj => 'Subject',
+                mesa => 'Message',
+                new  => 'New',
+                save => 'Save',
+                canc => 'Cancel',
+                kehi => 'Keyword Highlight Options',
+                txtc => 'Text Color',
+                font => 'Font Size',
+                fnst => 'Font Style',
+             );
+    &js_escape(\%js_lt);
+    &html_escape(\%html_js_lt);
+    &js_escape(\%html_js_lt);
+    $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
 
 //===================== Show list of keywords ====================
   function keywords(formname) {
-    var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value);
+    var nret = prompt("$js_lt{'keyw'}",formname.keywords.value);
     if (nret==null) return;
     formname.keywords.value = nret;
 
@@ -1464,10 +1590,10 @@ INNERJS
     else return;
     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
     if (cleantxt=="") {
-	alert("$alertmsg");
+	alert("$js_lt{'plse'}");
 	return;
     }
-    var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
+    var nret = prompt("$js_lt{'adds'}",cleantxt);
     if (nret==null) return;
     document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
     if (document.SCORE.keywords.value != "") {
@@ -1531,17 +1657,15 @@ INNERJS
 
   function savedMsgHeader(Nmsg,usrctr,fullname) {
     var height = 70*Nmsg+250;
-    var scrollbar = "no";
     if (height > 600) {
 	height = 600;
-	scrollbar = "yes";
     }
     var xpos = (screen.width-600)/2;
     xpos = (xpos < 0) ? '0' : xpos;
     var ypos = (screen.height-height)/2-30;
     ypos = (ypos < 0) ? '0' : ypos;
 
-    pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
+    pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars=yes,screenx='+xpos+',screeny='+ypos+',width=700,height='+height);
     pWin.focus();
     pDoc = pWin.document;
     pDoc.$docopen;
@@ -1549,42 +1673,41 @@ INNERJS
 
     pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
-    pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />");
+    pDoc.write("<h1>&nbsp;$html_js_lt{'comp'}\"+fullname+\"<\\/h1>");
 
-    pDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
-    pDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">');
-    pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>");
+    pDoc.write('<table style="border:1px solid black;"><tr>');
+    pDoc.write("<td><b>$html_js_lt{'incl'}<\\/b><\\/td><td><b>$html_js_lt{'type'}<\\/b><\\/td><td><b>$html_js_lt{'mesa'}<\\/td><\\/tr>");
 }
     function displaySubject(msg,shwsel) {
     pDoc = pWin.document;
-    pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
-    pDoc.write("<td>Subject<\\/td>");
+    pDoc.write("<tr>");
     pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
-    pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"><\\/td><\\/tr>");
+    pDoc.write("<td>$html_js_lt{'subj'}<\\/td>");
+    pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"40\\" maxlength=\\"80\\"><\\/td><\\/tr>");
 }
 
   function displaySavedMsg(ctr,msg,shwsel) {
     pDoc = pWin.document;
-    pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
-    pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
+    pDoc.write("<tr>");
     pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
+    pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
     pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");
 }
 
   function newMsg(newmsg,shwsel) {
     pDoc = pWin.document;
-    pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
-    pDoc.write("<td align=\\"center\\">New<\\/td>");
+    pDoc.write("<tr>");
     pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
+    pDoc.write("<td align=\\"center\\">$html_js_lt{'new'}<\\/td>");
     pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");
 }
 
   function msgTail() {
     pDoc = pWin.document;
-    pDoc.write("<\\/table>");
+    //pDoc.write("<\\/table>");
     pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
-    pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
-    pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
+    pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
+    pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\"><br /><br />");
     pDoc.write("<\\/form>");
     pDoc.write('$end_page_msg_central');
     pDoc.close();
@@ -1598,25 +1721,34 @@ INNERJS
     var redsel = "";
     var grnsel = "";
     var blusel = "";
-    if (kwclr=="red")   {var redsel="checked"};
-    if (kwclr=="green") {var grnsel="checked"};
-    if (kwclr=="blue")  {var blusel="checked"};
+    var txtcol1 = "$js_lt{'col1'}";
+    var txtcol2 = "$js_lt{'col2'}";
+    var txtcol3 = "$js_lt{'col3'}";
+    var txtsiz1 = "$js_lt{'siz1'}";
+    var txtsiz2 = "$js_lt{'siz2'}";
+    var txtsiz3 = "$js_lt{'siz3'}";
+    var txtsty1 = "$js_lt{'sty1'}";
+    var txtsty2 = "$js_lt{'sty2'}";
+    var txtsty3 = "$js_lt{'sty3'}";
+    if (kwclr=="red")   {var redsel="checked='checked'"};
+    if (kwclr=="green") {var grnsel="checked='checked'"};
+    if (kwclr=="blue")  {var blusel="checked='checked'"};
     var sznsel = "";
     var sz1sel = "";
     var sz2sel = "";
-    if (kwsize=="0")  {var sznsel="checked"};
-    if (kwsize=="+1") {var sz1sel="checked"};
-    if (kwsize=="+2") {var sz2sel="checked"};
+    if (kwsize=="0")  {var sznsel="checked='checked'"};
+    if (kwsize=="+1") {var sz1sel="checked='checked'"};
+    if (kwsize=="+2") {var sz2sel="checked='checked'"};
     var synsel = "";
     var syisel = "";
     var sybsel = "";
-    if (kwstyle=="")    {var synsel="checked"};
-    if (kwstyle=="<i>") {var syisel="checked"};
-    if (kwstyle=="<b>") {var sybsel="checked"};
+    if (kwstyle=="")    {var synsel="checked='checked'"};
+    if (kwstyle=="<i>") {var syisel="checked='checked'"};
+    if (kwstyle=="<b>") {var sybsel="checked='checked'"};
     highlightCentral();
-    highlightbody('red','red',redsel,'0','normal',sznsel,'','normal',synsel);
-    highlightbody('green','green',grnsel,'+1','+1',sz1sel,'<i>','italic',syisel);
-    highlightbody('blue','blue',blusel,'+2','+2',sz2sel,'<b>','bold',sybsel);
+    highlightbody('red',txtcol1,redsel,'0',txtsiz1,sznsel,'',txtsty1,synsel);
+    highlightbody('green',txtcol2,grnsel,'+1',txtsiz2,sz1sel,'<i>',txtsty2,syisel);
+    highlightbody('blue',txtcol3,blusel,'+2',txtsiz3,sz2sel,'<b>',txtsty3,sybsel);
     highlightend();
     return;
   }
@@ -1634,37 +1766,34 @@ INNERJS
     hDoc.$docopen;
     hDoc.write('$start_page_highlight_central');
     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
-    hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options<\\/span><\\/h3><br /><br />");
+    hDoc.write("<h1>$html_js_lt{'kehi'}<\\/h1>");
 
-    hDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
-    hDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">');
-    hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>");
+    hDoc.write('<table border="0" width="100%"><tr style="background-color:#A1D676">');
+    hDoc.write("<th>$html_js_lt{'txtc'}<\\/th><th>$html_js_lt{'font'}<\\/th><th>$html_js_lt{'fnst'}<\\/th><\\/tr>");
   }
 
   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
     var hDoc = hwdWin.document;
-    hDoc.write("<tr bgcolor=\\"#ffffdd\\">");
+    hDoc.write("<tr>");
     hDoc.write("<td align=\\"left\\">");
-    hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"<\\/td>");
+    hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+" \\/>&nbsp;"+clrtxt+"<\\/td>");
     hDoc.write("<td align=\\"left\\">");
-    hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"<\\/td>");
+    hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+" \\/>&nbsp;"+sztxt+"<\\/td>");
     hDoc.write("<td align=\\"left\\">");
-    hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"<\\/td>");
+    hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+" \\/>&nbsp;"+sytxt+"<\\/td>");
     hDoc.write("<\\/tr>");
   }
 
   function highlightend() { 
     var hDoc = hwdWin.document;
-    hDoc.write("<\\/table>");
-    hDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
-    hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");
-    hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
+    hDoc.write("<\\/table><br \\/>");
+    hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:updateChoice(1)\\" \\/>&nbsp;&nbsp;");
+    hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\" \\/><br /><br />");
     hDoc.write("<\\/form>");
     hDoc.write('$end_page_highlight_central');
     hDoc.close();
   }
 
-</script>
 SUBJAVASCRIPT
 }
 
@@ -1677,6 +1806,25 @@ sub get_increment {
     return $increment;
 }
 
+sub gradeBox_start {
+    return (
+        &Apache::loncommon::start_data_table()
+       .&Apache::loncommon::start_data_table_header_row()
+       .'<th>'.&mt('Part').'</th>'
+       .'<th>'.&mt('Points').'</th>'
+       .'<th>&nbsp;</th>'
+       .'<th>'.&mt('Assign Grade').'</th>'
+       .'<th>'.&mt('Weight').'</th>'
+       .'<th>'.&mt('Grade Status').'</th>'
+       .&Apache::loncommon::end_data_table_header_row()
+    );
+}
+
+sub gradeBox_end {
+    return (
+        &Apache::loncommon::end_data_table()
+    );
+}
 #--- displays the grading box, used in essay type problem and grading by page/sequence
 sub gradeBox {
     my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
@@ -1688,7 +1836,7 @@ sub gradeBox {
     $wgt       = ($wgt > 0 ? $wgt : '1');
     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
 		  '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
-    my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
+    my $data_WGT='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
     my $display_part= &get_display_part($partid,$symb);
     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
 				       [$partid]);
@@ -1696,7 +1844,7 @@ sub gradeBox {
     if ($last_resets{$partid}) {
         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
     }
-    $result.='<table border="0"><tr>';
+    my $result=&Apache::loncommon::start_data_table_row();
     my $ctr = 0;
     my $thisweight = 0;
     my $increment = &get_increment();
@@ -1704,7 +1852,7 @@ sub gradeBox {
     my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
     while ($thisweight<=$wgt) {
 	$radio.= '<td><span class="LC_nobreak"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
-	    'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
+        'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
 	    $thisweight.')" value="'.$thisweight.'" '.
 	    ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
 	$radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
@@ -1715,13 +1863,13 @@ sub gradeBox {
 
     my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
 	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
-	'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
+	'onchange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
 	$wgt.')" /></td>'."\n";
     $line.='<td>/'.$wgt.' '.$wgtmsg.
 	($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
-	' </td><td><b>'.&mt('Grade Status').':</b>'."\n";
-    $line.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
-	'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
+	' </td>'."\n";
+    $line.='<td><select name="GD_SEL'.$counter.'_'.$partid.'" '.
+	'onchange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
 	$line.='<option></option>'.
 	    '<option value="excused" selected="selected">'.&mt('excused').'</option>';
@@ -1732,10 +1880,10 @@ sub gradeBox {
     $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
 
 
-	#&mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line);
     $result .= 
-	    '<td><b>'.&mt('Part').':</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points').':</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>';
-    $result.='</tr></table>'."\n";
+	    '<td>'.$data_WGT.$display_part.'</td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>';
+    $result.=&Apache::loncommon::end_data_table_row();
+    $result.=&Apache::loncommon::start_data_table_row().'<td colspan="6">';
     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
 	'<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
 	'<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
@@ -1744,15 +1892,20 @@ sub gradeBox {
         $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
         $aggtries.'" />'."\n";
-    $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);
+    my $res_error;
+    $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record,\$res_error);
+    $result.='</td>'.&Apache::loncommon::end_data_table_row();
+    if ($res_error) {
+        return &navmap_errormsg();
+    }
     return $result;
 }
 
 sub handback_box {
-    my ($symb,$uname,$udom,$counter,$partid,$record) = @_;
-    my ($partlist,$handgrade,$responseType) = &response_type($symb);
+    my ($symb,$uname,$udom,$counter,$partid,$record,$res_error_pointer) = @_;
+    my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error_pointer);
     my (@respids);
-     my @part_response_id = &flatten_responseType($responseType);
+    my @part_response_id = &flatten_responseType($responseType);
     foreach my $part_response_id (@part_response_id) {
     	my ($part,$resp) = @{ $part_response_id };
         if ($part eq $partid) {
@@ -1764,21 +1917,25 @@ sub handback_box {
 	my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
 	my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
 	next if (!@$files);
-	my $file_counter = 1;
+	my $file_counter = 0;
 	foreach my $file (@$files) {
 	    if ($file =~ /\/portfolio\//) {
+                $file_counter++;
     	        my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
-    	        my ($name,$version,$ext) = &file_name_version_ext($file_disp);
+    	        my ($name,$version,$ext) = &Apache::lonnet::file_name_version_ext($file_disp);
     	        $file_disp = "$name.$ext";
     	        $file = $file_path.$file_disp;
     	        $result.=&mt('Return commented version of [_1] to student.',
     			 '<span class="LC_filename">'.$file_disp.'</span>');
     	        $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
-    	        $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />';
-    	        $result.='('.&mt('File will be uploaded when you click on Save &amp; Next below.').')<br />';
-    	        $file_counter++;
+    	        $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />'."\n";
 	    }
 	}
+        if ($file_counter) {
+            $result .= '<input type="hidden" name="'.$prefix.'countreturndoc" value="'.$file_counter.'" />'."\n".
+                       '<span class="LC_info">'.
+                       '('.&mt('File(s) will be uploaded when you click on Save &amp; Next below.',$file_counter).')</span><br /><br />';
+        }
     }
     return $result;    
 }
@@ -1811,34 +1968,43 @@ sub show_problem {
 	$companswer=~s|</form>||g;
 	$companswer=~s|name="submit"|name="would_have_been_submit"|g;
     }
+    my $renderheading = &mt('View of the problem');
+    my $answerheading = &mt('Correct answer');
+    if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
+        my $stu_fullname = $env{'form.fullname'};
+        if ($stu_fullname eq '') {
+            $stu_fullname = &Apache::loncommon::plainname($uname,$udom,'lastname');
+        }
+        my $forwhom = &nameUserString(undef,$stu_fullname,$uname,$udom);
+        if ($forwhom ne '') {
+            $renderheading = &mt('View of the problem for[_1]',$forwhom);
+            $answerheading = &mt('Correct answer for[_1]',$forwhom);
+        }
+    }
     $rendered=
-	'<div class="LC_grade_show_problem_header">'.
-	&mt('View of the problem').
-	'</div><div class="LC_grade_show_problem_problem">'.
-	$rendered.
-	'</div>';
+        '<div class="LC_Box">'
+       .'<h3 class="LC_hcell">'.$renderheading.'</h3>'
+       .$rendered
+       .'</div>';
     $companswer=
-	'<div class="LC_grade_show_problem_header">'.
-	&mt('Correct answer').
-	'</div><div class="LC_grade_show_problem_problem">'.
-	$companswer.
-	'</div>';
+        '<div class="LC_Box">'
+       .'<h3 class="LC_hcell">'.$answerheading.'</h3>'
+       .$companswer
+       .'</div>';
     my $result;
     if ($mode eq 'both') {
-	$result=$rendered.$companswer;
+        $result=$rendered.$companswer;
     } elsif ($mode eq 'text') {
-	$result=$rendered;
+        $result=$rendered;
     } elsif ($mode eq 'answer') {
-	$result=$companswer;
+        $result=$companswer;
     }
-    $result='<div class="LC_grade_show_problem">'.$result.'</div>';
     return $result;
 }
 
 sub files_exist {
     my ($r, $symb) = @_;
     my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
-
     foreach my $student (@students) {
         my ($uname,$udom,$fullname) = split(/:/,$student);
         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
@@ -1857,6 +2023,10 @@ sub files_exist {
 
 sub download_all_link {
     my ($r,$symb) = @_;
+    unless (&files_exist($r, $symb)) {
+       $r->print(&mt('There are currently no submitted documents.'));
+       return;
+    }
     my $all_students = 
 	join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
 
@@ -1869,7 +2039,62 @@ sub download_all_link {
                              'cgi.'.$identifier.'.parts' => $parts,});
     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
 	      &mt('Download All Submitted Documents').'</a>');
-    return
+    return;
+}
+
+sub submit_download_link {
+    my ($request,$symb) = @_;
+    if (!$symb) { return ''; }
+#FIXME: Figure out which type of problem this is and provide appropriate download
+    my $res_error;
+    my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error);
+    if (ref($res_error)) {
+        if ($$res_error) {
+            $request->print(&mt('An error occurred retrieving response types'));
+            return;
+        }
+    }
+    my ($numupload,$numessay) = (0,0);
+    if (ref($responseType) eq 'HASH') {
+        foreach my $part (sort(keys(%$responseType))) {
+            foreach my $id (sort(keys(%{ $responseType->{$part} }))) {
+                my $responsetype = $responseType->{$part}->{$id};
+                if ($responsetype eq 'essay') {
+                    my $uploadedfiletypes =
+                        &Apache::lonnet::EXT("resource.$part".'_'."$id.uploadedfiletypes",$symb);
+                    if ($uploadedfiletypes) {
+                        $numupload++;
+                    } else {
+                        $numessay++;
+                    }
+                }
+            }
+        }
+    }
+    if (($numupload) || ($numessay)) {
+        my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
+        my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
+        my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
+        (undef,undef,my $fullname) = &getclasslist($getsec,1,$getgroup,$symb,$submitonly,1);
+        if (ref($fullname) eq 'HASH') {
+            my @students = map { $_.':'.$fullname->{$_} } (keys(%{$fullname}));
+            if (@students) {
+                @{$env{'form.stuinfo'}} = @students;
+                if ($numupload) {
+                    &download_all_link($request,$symb);
+                }
+# FIXME Need to provide a mechanism to download essays, i.e., if $numessay > 0
+# Needs to omit user's identity if resource instance is for an anonymous survey.
+            } else {
+                $request->print(&mt('No students match the criteria you selected'));
+            }
+        } else {
+            $request->print(&mt('Could not retrieve student information'));
+        }
+    } else {
+        $request->print(&mt('No essayresponse items found'));
+    }
+    return;
 }
 
 sub build_section_inputs {
@@ -1887,42 +2112,41 @@ sub build_section_inputs {
 
 # --------------------------- show submissions of a student, option to grade 
 sub submission {
-    my ($request,$counter,$total) = @_;
+    my ($request,$counter,$total,$symb) = @_;
     my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
     $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
     $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
-    my $symb = &get_symb($request); 
+
+    my $probtitle=&Apache::lonnet::gettitle($symb); 
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
+    my $is_tool = ($symb =~ /ext\.tool$/);
+    my ($essayurl,%coursedesc_by_cid);
 
     if (!&canview($usec)) {
-	$request->print('<span class="LC_warning">Unable to view requested student.('.
-			$uname.':'.$udom.' in section '.$usec.' in course id '.
-			$env{'request.course.id'}.')</span>');
-	$request->print(&show_grading_menu_form($symb));
+        $request->print(
+            '<span class="LC_warning">'.
+            &mt('Unable to view requested student.').
+            ' '.&mt('([_1] in section [_2] in course id [_3])',
+                        $uname.':'.$udom,$usec,$env{'request.course.id'}).
+            '</span>');
 	return;
     }
 
     if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
-    if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
-    if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
+    unless ($is_tool) { 
+        if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
+        if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
+    }
     my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
     my $checkIcon = '<img alt="'.&mt('Check Mark').
 	'" src="'.$request->dir_config('lonIconsURL').
 	'/check.gif" height="16" border="0" />';
 
-    my %old_essays;
     # header info
     if ($counter == 0) {
 	&sub_page_js($request);
-	&sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes');
-	$env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
-	    &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
-	if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {
-	    &download_all_link($request, $symb);
-	}
-	$request->print('<h3>&nbsp;<span class="LC_info">'.&mt('Submission Record').'</span></h3>'."\n".
-			'<h4>&nbsp;'.&mt('<b>Resource: </b> [_1]',$env{'form.probTitle'}).'</h4>'."\n");
+	&sub_page_kw_js($request);
 
 	# option to display problem, only once else it cause problems 
         # with the form later since the problem has a form.
@@ -1939,10 +2163,11 @@ sub submission {
 	    $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
 	}
 
-	# kwclr is the only variable that is guaranteed to be non blank 
+	# kwclr is the only variable that is guaranteed not to be blank 
         # if this subroutine has been called once.
 	my %keyhash = ();
-	if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
+#	if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
+        if (1) {
 	    %keyhash = &Apache::lonnet::dump('nohist_handgrade',
 					     $env{'course.'.$env{'request.course.id'}.'.domain'},
 					     $env{'course.'.$env{'request.course.id'}.'.num'});
@@ -1953,31 +2178,28 @@ sub submission {
 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
 	    $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
-		$keyhash{$symb.'_subject'} : $env{'form.probTitle'};
+		$keyhash{$symb.'_subject'} : $probtitle;
 	    $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
 	}
 	my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
 	my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
 	$request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
 			'<input type="hidden" name="command"    value="handgrade" />'."\n".
-			'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
 			'<input type="hidden" name="Status"     value="'.$stu_status.'" />'."\n".
 			'<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
-			'<input type="hidden" name="probTitle"  value="'.$env{'form.probTitle'}.'" />'."\n".
 			'<input type="hidden" name="refresh"    value="off" />'."\n".
 			'<input type="hidden" name="studentNo"  value="" />'."\n".
 			'<input type="hidden" name="gradeOpt"   value="" />'."\n".
 			'<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
-			'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".
 			'<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
 			'<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
 			'<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
 			&build_section_inputs().
 			'<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
-			'<input type="hidden" name="handgrade"  value="'.$env{'form.handgrade'}.'" />'."\n".
 			'<input type="hidden" name="NCT"'.
 			' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
-	if ($env{'form.handgrade'} eq 'yes') {
+#	if ($env{'form.handgrade'} eq 'yes') {
+        if (1) {
 	    $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
 			    '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
 			    '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
@@ -2002,35 +2224,81 @@ sub submission {
 	}
 	$request->print($prnmsg);
 
-	if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {
+#	if ($env{'form.handgrade'} eq 'yes') {
+        unless ($is_tool) {
+
+            my %lt = &Apache::lonlocal::texthash(
+                          keyh => 'Keyword Highlighting for Essays',
+                          keyw => 'Keyword Options',
+                          list => 'List',
+                          past => 'Paste Selection to List',
+                          high => 'Highlight Attribute',
+                     );    
 #
 # Print out the keyword options line
 #
-	    $request->print(<<KEYWORDS);
-&nbsp;<b>Keyword Options:</b>&nbsp;
-<a href="javascript:keywords(document.SCORE);" target="_self">List</a>&nbsp; &nbsp;
-<a href="#" onMouseDown="javascript:getSel(); return false"
- CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
-<a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br />
-KEYWORDS
+	    $request->print(
+                '<div class="LC_columnSection">'
+               .'<fieldset><legend>'.$lt{'keyh'}.'</legend>'
+               .&Apache::lonhtmlcommon::funclist_from_array(
+                    ['<a href="javascript:keywords(document.SCORE);" target="_self">'.$lt{'list'}.'</a>',
+                     '<a href="#" onmousedown="javascript:getSel(); return false"
+ class="page">'.$lt{'past'}.'</a>',
+                     '<a href="javascript:kwhighlight();" target="_self">'.$lt{'high'}.'</a>'],
+                    {legend => $lt{'keyw'}})
+               .'</fieldset></div>'
+            );
+
 #
 # Load the other essays for similarity check
 #
-            my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
-	    my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
-	    $apath=&escape($apath);
-	    $apath=~s/\W/\_/gs;
-	    %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
+            (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
+            if ($essayurl eq 'lib/templates/simpleproblem.problem') {
+                my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+                my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+                if ($cdom ne '' && $cnum ne '') {
+                    my ($map,$id,$res) = &Apache::lonnet::decode_symb($symb);
+                    if ($map =~ m{^\Quploaded/$cdom/$cnum/\E(default(?:|_\d+)\.(?:sequence|page))$}) {
+                        my $apath = $1.'_'.$id;
+                        $apath=~s/\W/\_/gs;
+                        &init_old_essays($symb,$apath,$cdom,$cnum);
+                    }
+                }
+            } else {
+	        my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
+	        $apath=&escape($apath);
+	        $apath=~s/\W/\_/gs;
+                &init_old_essays($symb,$apath,$adom,$aname);
+            }
         }
     }
 
 # This is where output for one specific student would start
-    my $add_class = ($counter%2) ? 'LC_grade_show_user_odd_row' : '';
-    $request->print("\n\n".
-                    '<div class="LC_grade_show_user '.$add_class.'">'.
-		    '<div class="LC_grade_user_name">'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</div>'.
-		    '<div class="LC_grade_show_user_body">'."\n");
+    my $add_class = ($counter%2) ? ' LC_grade_show_user_odd_row' : '';
+    $request->print(
+        "\n\n"
+       .'<div class="LC_grade_show_user'.$add_class.'">'
+       .'<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</h2>'
+       ."\n"
+    );
+
+    # Show additional functions if allowed
+    if ($perm{'vgr'}) {
+        $request->print(
+            &Apache::loncommon::track_student_link(
+                'View recent activity',
+                $uname,$udom,'check')
+           .' '
+        );
+    }
+    if ($perm{'opa'}) {
+        $request->print(
+            &Apache::loncommon::pprmlink(
+                &mt('Set/Change parameters'),
+                $uname,$udom,$symb,'check'));
+    }
 
+    # Show Problem
     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
 	my $mode;
 	if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
@@ -2045,166 +2313,242 @@ KEYWORDS
     }
 
     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
-    my ($partlist,$handgrade,$responseType) = &response_type($symb);
+    my $res_error;
+    my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
+    if ($res_error) {
+        $request->print(&navmap_errormsg());
+        return;
+    }
 
     # Display student info
     $request->print(($counter == 0 ? '' : '<br />'));
-    my $result='<div class="LC_grade_submissions">';
-    
-    $result.='<div class="LC_grade_submissions_header">';
-    $result.= &mt('Submissions');
-    $result.='<input type="hidden" name="name'.$counter.
-	'" value="'.$env{'form.fullname'}.'" />'."\n";
-    if ($env{'form.handgrade'} eq 'no') {
-	$result.='<span class="LC_grade_check_note">'.
-	    &mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)."</span>\n";
 
+    my $boxtitle = &mt('Submissions');
+    if ($is_tool) {
+        $boxtitle = &mt('Transactions')
+    }
+    my $result='<div class="LC_Box">'
+              .'<h3 class="LC_hcell">'.$boxtitle.'</h3>';
+    $result.='<input type="hidden" name="name'.$counter.
+             '" value="'.$env{'form.fullname'}.'" />'."\n";
+#    if ($env{'form.handgrade'} eq 'no') {
+    unless ($is_tool) {
+        $result.='<p class="LC_info">'
+                .&mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)
+                ."</p>\n";
     }
-
-
 
     # If any part of the problem is an essay-response (handgraded), then check for collaborators
     my $fullname;
     my $col_fullnames = [];
-    if ($env{'form.handgrade'} eq 'yes') {
+#    if ($env{'form.handgrade'} eq 'yes') {
+    unless ($is_tool) {
 	(my $sub_result,$fullname,$col_fullnames)=
 	    &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
 				 $counter);
 	$result.=$sub_result;
     }
     $request->print($result."\n");
-    $request->print('</div>'."\n");
+    
     # print student answer/submission
-    # Options are (1) Handgaded submission only
+    # Options are (1) Handgraded submission only
     #             (2) Last submission, includes submission that is not handgraded 
     #                  (for multi-response type part)
     #             (3) Last submission plus the parts info
     #             (4) The whole record for this student
-    if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
-	my ($string,$timestamp)= &get_last_submission(\%record);
+    
+    my ($string,$timestamp)= &get_last_submission(\%record,$is_tool);
 	
-	my $lastsubonly;
-
-	if ($$timestamp eq '') {
-	    $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
-	} else {
-	    $lastsubonly = '<div class="LC_grade_submissions_body"> <b>Date Submitted:</b> '.$$timestamp."\n";
+    my $lastsubonly;
 
-	    my %seenparts;
-	    my @part_response_id = &flatten_responseType($responseType);
-	    foreach my $part (@part_response_id) {
-		next if ($env{'form.lastSub'} eq 'hdgrade' 
+    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) {
+	    next if ($env{'form.lastSub'} eq 'hdgrade' 
 			 && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
 
-		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;
-		    my $submitby='<b>Part:</b> '.$display_part.
-			' <b>Collaborative submission by:</b> '.
-			'<a href="javascript:viewSubmitter(\''.
-			$env{"form.$uname:$udom:$partid:submitted_by"}.
-			'\');" target="_self">'.
-			$$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
-		    $request->print($submitby);
-		    next;
-		}
-		my $responsetype = $responseType->{$partid}->{$respid};
-		if (!exists($record{"resource.$partid.$respid.submission"})) {
-		    $lastsubonly.="\n".'<div class="LC_grade_submission_part"><b>Part:</b> '.
-			$display_part.' <span class="LC_internal_info">( ID '.$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,$subval) = split(/:/,$submission,2);
-		    # Similarity check
-		    my $similar='';
-		    if($env{'form.checkPlag'}){
-			my ($oname,$odom,$ocrsid,$oessay,$osim)=
-			    &most_similar($uname,$udom,$subval,\%old_essays);
-			if ($osim) {
-			    $osim=int($osim*100.0);
-			    my %old_course_desc = 
-				&Apache::lonnet::coursedescription($ocrsid,
-								   {'one_time' => 1});
-
-			    $similar="<hr /><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><blockquote><i>'.
-				&keywords_highlight($oessay).
-				'</i></blockquote><hr />';
-			}
-		    }
-		    my $order=&get_order($partid,$respid,$symb,$uname,$udom);
-		    if ($env{'form.lastSub'} eq 'lastonly' || 
-			($env{'form.lastSub'} eq 'hdgrade' && 
+	    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)$/ || ($env{'form.lastSub'} eq 'hdgrade' && 
 			 $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
-			my $display_part=&get_display_part($partid,$symb);
-			$lastsubonly.='<div class="LC_grade_submission_part"><b>Part:</b> '.
-			    $display_part.' <span class="LC_internal_info">( ID '.$respid.
-			    ' )</span>&nbsp; &nbsp;';
-			my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
-			if (@$files) {
-			    $lastsubonly.='<br /><span class="LC_warning">'.&mt('Like all files provided by users, this file may contain viruses').'</span><br />';
-			    my $file_counter = 0;
-			    foreach my $file (@$files) {
-			        $file_counter++;
-				&Apache::lonnet::allowuploaded('/adm/grades',$file);
-				$lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" /> '.$file.'</a>';
-			    }
-			    $lastsubonly.='<br />';
-			}
-			$lastsubonly.='<b>'.&mt('Submitted Answer:').' </b>'.
+		    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);
-			if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
-			$lastsubonly.='</div>';
-		    }
+					 $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";
+            }
 	}
-	$request->print($lastsubonly);
-   } elsif ($env{'form.lastSub'} eq 'datesub') {
-	my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);
+	$lastsubonly.='</div>'."\n"; # End: LC_grade_submissions_body
+    }
+    $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));
-    } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
-	$request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
+  
+    } 
+    if ($env{'form.lastSub'} =~ /^(last|all)$/) {
+        my $identifier = (&canmodify($usec)? $counter : '');
+        $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
 								 $env{'request.course.id'},
 								 $last,'.submission',
-								 'Apache::grades::keywords_highlight'));
+								 'Apache::grades::keywords_highlight',
+                                                                 $usec,$identifier));
     }
-
     $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
 	.$udom.'" />'."\n");
     # return if view submission with no grading option
-    if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
-	my $toGrade.='<input type="button" value="Grade Student" '.
-	    'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
-	    .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
-	$toGrade.='</div>'."\n";
-	if (($env{'form.command'} eq 'submission') || 
-	    ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
-	    $toGrade.='</form>'.&show_grading_menu_form($symb); 
-	}
-	$request->print($toGrade);
+    if (!&canmodify($usec)) {
+	$request->print('<p><span class="LC_warning">'.&mt('No grading privileges').'</span></p></div>');
 	return;
     } else {
 	$request->print('</div>'."\n");
     }
 
     # essay grading message center
-    if ($env{'form.handgrade'} eq 'yes') {
+#    if ($env{'form.handgrade'} eq 'yes') {
+    if (1) {
 	my $result='<div class="LC_grade_message_center">';
     
 	$result.='<div class="LC_grade_message_center_header">'.
@@ -2220,10 +2564,10 @@ KEYWORDS
 	    '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
 	$result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
 	    ',\''.$msgfor.'\');" target="_self">'.
-	    &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').'</a><label> ('.
+	    &mt('Compose message to student'.(scalar(@$col_fullnames) >= 1 ? 's' : '')).'</a><label> ('.
 	    &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
-	    '<img src="'.$request->dir_config('lonIconsURL').
-	    '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
+	    ' <img src="'.$request->dir_config('lonIconsURL').
+	    '/mailbkgrd.gif" width="14" height="10" alt="" name="mailicon'.$counter.'" />'."\n".
 	    '<br />&nbsp;('.
 	    &mt('Message will be sent when you click on Save &amp; Next below.').")\n";
 	$result.='</div></div>';
@@ -2233,12 +2577,17 @@ KEYWORDS
     my %seen = ();
     my @partlist;
     my @gradePartRespid;
-    my @part_response_id = &flatten_responseType($responseType);
-    $request->print('<div class="LC_grade_assign">'.
-		    
-		    '<div class="LC_grade_assign_header">'.
-		    &mt('Assign Grades').'</div>'.
-		    '<div class="LC_grade_assign_body">');
+    my @part_response_id;
+    if ($is_tool) {
+        @part_response_id = ([0,'']);
+    } else {
+        @part_response_id = &flatten_responseType($responseType);
+    }
+    $request->print(
+        '<div class="LC_Box">'
+       .'<h3 class="LC_hcell">'.&mt('Assign Grades').'</h3>'
+    );
+    $request->print(&gradeBox_start());
     foreach my $part_response_id (@part_response_id) {
     	my ($partid,$respid) = @{ $part_response_id };
 	my $part_resp = join('_',@{ $part_response_id });
@@ -2250,19 +2599,10 @@ KEYWORDS
 	push(@gradePartRespid,$partid.'.'.$respid);
 	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
     }
-    $request->print('</div></div>');
+    $request->print(&gradeBox_end()); # </div>
+    $request->print('</div>');
 
     $request->print('<div class="LC_grade_info_links">');
-    if ($perm{'vgr'}) {
-	$request->print(
-	    &Apache::loncommon::track_student_link(&mt('View recent activity'),
-						   $uname,$udom,'check'));
-    }
-    if ($perm{'opa'}) {
-	$request->print(
-	    &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),
-					 $uname,$udom,$symb,'check'));
-    }
     $request->print('</div>');
 
     $result='<input type="hidden" name="partlist'.$counter.
@@ -2279,15 +2619,14 @@ KEYWORDS
 
 # Done with printing info for one student
 
-    $request->print('</div>');#LC_grade_show_user_body
     $request->print('</div>');#LC_grade_show_user
 
 
     # print end of form
     if ($counter == $total) {
-	my $endform='<table border="0"><tr><td>'."\n";
+        my $endform='<br /><hr /><table border="0"><tr><td>'."\n";
 	$endform.='<input type="button" value="'.&mt('Save &amp; Next').'" '.
-	    'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
+	    'onclick="javascript:checksubmit(this.form,\'Save & Next\','.
 	    $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
 	my $ntstu ='<select name="NTSTU">'.
 	    '<option>1</option><option>2</option>'.
@@ -2297,14 +2636,15 @@ KEYWORDS
 	$ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
         $endform.=&mt('[_1]student(s)',$ntstu);
 	$endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('Previous').'" '.
-	    'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
+	    'onclick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
 	    '<input type="button" value="'.&mt('Next').'" '.
-	    'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
-	$endform.=&mt('(Next and Previous (student) do not save the scores.)')."\n" ;
+	    'onclick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
+        $endform.='<span class="LC_warning">'.
+                  &mt('(Next and Previous (student) do not save the scores.)').
+                  '</span>'."\n" ;
         $endform.="<input type='hidden' value='".&get_increment().
             "' name='increment' />";
 	$endform.='</td></tr></table></form>';
-	$endform.=&show_grading_menu_form($symb);
 	$request->print($endform);
     }
     return '';
@@ -2323,10 +2663,10 @@ sub check_collaborators {
 	next if ($record->{'resource.'.$part.'.collaborators'} eq '');
 	my (@good_collaborators, @bad_collaborators);
 	foreach my $possible_collaborator
-	    (split(/,?\s+/,$record->{'resource.'.$part.'.collaborators'})) { 
+	    (split(/[,;\s]+/,$record->{'resource.'.$part.'.collaborators'})) { 
 	    $possible_collaborator =~ s/[\$\^\(\)]//g;
 	    next if ($possible_collaborator eq '');
-	    my ($co_name,$co_dom) = split(/\@|:/,$possible_collaborator);
+	    my ($co_name,$co_dom) = split(/:/,$possible_collaborator);
 	    $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
 	    next if ($co_name eq $uname && $co_dom eq $udom);
 	    # Doing this grep allows 'fuzzy' specification
@@ -2339,13 +2679,13 @@ sub check_collaborators {
 	    }
 	}
 	if (scalar(@good_collaborators) != 0) {
-	    $result.='<br />'.&mt('Collaborators: ');
+	    $result.='<br />'.&mt('Collaborators:').'<ol>';
 	    foreach my $name (@good_collaborators) {
 		my ($lastname,$givenn) = split(/,/,$$fullname{$name});
 		push(@col_fullnames, $givenn.' '.$lastname);
-		$result.=$fullname->{$name}.'&nbsp; &nbsp; &nbsp;';
+		$result.='<li>'.$fullname->{$name}.'</li>';
 	    }
-	    $result.='<br />'."\n";
+	    $result.='</ol><br />'."\n";
 	    my ($part)=split(/\./,$part);
 	    $result.='<input type="hidden" name="collaborator'.$counter.
 		'" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
@@ -2368,8 +2708,8 @@ sub check_collaborators {
 
 #--- Retrieve the last submission for all the parts
 sub get_last_submission {
-    my ($returnhash)=@_;
-    my (@string,$timestamp);
+    my ($returnhash,$is_tool)=@_;
+    my (@string,$timestamp,%lasthidden);
     if ($$returnhash{'version'}) {
 	my %lasthash=();
 	my ($version);
@@ -2381,18 +2721,67 @@ sub get_last_submission {
 		    &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
 	    }
 	}
+        my (%typeparts,%randombytry);
+        my $showsurv = 
+            &Apache::lonnet::allowed('vas',$env{'request.course.id'});
+        foreach my $key (sort(keys(%lasthash))) {
+            if ($key =~ /\.type$/) {
+                if (($lasthash{$key} eq 'anonsurvey') || 
+                    ($lasthash{$key} eq 'anonsurveycred') ||
+                    ($lasthash{$key} eq 'randomizetry')) {
+                    my ($ign,@parts) = split(/\./,$key);
+                    pop(@parts);
+                    my $id = join('.',@parts);
+                    if ($lasthash{$key} eq 'randomizetry') {
+                        $randombytry{$ign.'.'.$id} = $lasthash{$key};
+                    } else {
+                        unless ($showsurv) {
+                            $typeparts{$ign.'.'.$id} = $lasthash{$key};
+                        }
+                    }
+                    delete($lasthash{$key});
+                }
+            }
+        }
+        my @hidden = keys(%typeparts);
+        my @randomize = keys(%randombytry);
 	foreach my $key (keys(%lasthash)) {
 	    next if ($key !~ /\.submission$/);
-
+            my $hide;
+            if (@hidden) {
+                foreach my $id (@hidden) {
+                    if ($key =~ /^\Q$id\E/) {
+                        $hide = 'anon';
+                        last;
+                    }
+                }
+            }
+            unless ($hide) {
+                if (@randomize) {
+                    foreach my $id (@randomize) {
+                        if ($key =~ /^\Q$id\E/) {
+                            $hide = 'rand';
+                            last;
+                        }
+                    }
+                }
+            }
 	    my ($partid,$foo) = split(/submission$/,$key);
-	    my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
-		'<span class="LC_warning">Draft Copy</span> ' : '';
-	    push(@string, join(':', $key, $draft.$lasthash{$key}));
+	    my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? 1 : 0;
+            push(@string, join(':', $key, $hide, $draft, (
+                ref($lasthash{$key}) eq 'ARRAY' ?
+                    join(',', @{$lasthash{$key}}) : $lasthash{$key}) ));
 	}
     }
     if (!@string) {
+        my $msg;
+        if ($is_tool) {
+            $msg = &mt('No grade passed back.');
+        } else {
+            $msg = &mt('Nothing submitted - no attempts.');
+        }
 	$string[0] =
-	    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span>';
+	    '<span class="LC_warning">'.$msg.'</span>';
     }
     return (\@string,\$timestamp);
 }
@@ -2410,10 +2799,191 @@ sub keywords_highlight {
     return $string;
 }
 
+# For Tasks provide a mechanism to display previous version for one specific student
+
+sub show_previous_task_version {
+    my ($request,$symb) = @_;
+    if ($symb eq '') {
+        $request->print(
+            '<span class="LC_error">'.
+            &mt('Unable to handle ambiguous references.').
+            '</span>');
+        return '';
+    }
+    my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'});
+    my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
+    if (!&canview($usec)) {
+        $request->print(
+            '<span class="LC_warning">'.
+            &mt('Unable to view previous version for requested student.').
+            ' '.&mt('([_1] in section [_2] in course id [_3])',
+                    $uname.':'.$udom,$usec,$env{'request.course.id'}).
+            '</span>');
+        return;
+    }
+    my $mode = 'both';
+    my $isTask = ($symb =~/\.task$/);
+    if ($isTask) {
+        if ($env{'form.previousversion'} =~ /^\d+$/) {
+            if ($env{'form.fullname'} eq '') {
+                $env{'form.fullname'} =
+                    &Apache::loncommon::plainname($uname,$udom,'lastname');
+            }
+            my $probtitle=&Apache::lonnet::gettitle($symb);
+            $request->print("\n\n".
+                            '<div class="LC_grade_show_user">'.
+                            '<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
+                            '</h2>'."\n");
+            &Apache::lonxml::clear_problem_counter();
+            $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,
+                            {'previousversion' => $env{'form.previousversion'} }));
+            $request->print("\n</div>");
+        }
+    }
+    return;
+}
+
+sub choose_task_version_form {
+    my ($symb,$uname,$udom,$nomenu) = @_;
+    my $isTask = ($symb =~/\.task$/);
+    my ($current,$version,$result,$js,$displayed,$rowtitle);
+    if ($isTask) {
+        my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
+                                              $udom,$uname);
+        if (($record{'resource.0.version'} eq '') ||
+            ($record{'resource.0.version'} < 2)) {
+            return ($record{'resource.0.version'},
+                    $record{'resource.0.version'},$result,$js);
+        } else {
+            $current = $record{'resource.0.version'};
+        }
+        if ($env{'form.previousversion'}) {
+            $displayed = $env{'form.previousversion'};
+            $rowtitle = &mt('Choose another version:')
+        } else {
+            $displayed = $current;
+            $rowtitle = &mt('Show earlier version:');
+        }
+        $result = '<div class="LC_left_float">';
+        my $list;
+        my $numversions = 0;
+        for (my $i=1; $i<=$record{'resource.0.version'}; $i++) {
+            if ($i == $current) {
+                if (!$env{'form.previousversion'} || $nomenu) {
+                    next;
+                } else {
+                    $list .= '<option value="'.$i.'">'.&mt('Current').'</option>'."\n";
+                    $numversions ++;
+                }
+            } elsif (defined($record{'resource.'.$i.'.0.status'})) {
+                unless ($i == $env{'form.previousversion'}) {
+                    $numversions ++;
+                }
+                $list .= '<option value="'.$i.'">'.$i.'</option>'."\n";
+            }
+        }
+        if ($numversions) {
+            $symb = &HTML::Entities::encode($symb,'<>"&');
+            $result .=
+                '<form name="getprev" method="post" action=""'.
+                ' onsubmit="return previousVersion('."'$uname','$udom','$symb','$displayed'".');">'.
+                &Apache::loncommon::start_data_table().
+                &Apache::loncommon::start_data_table_row().
+                '<th align="left">'.$rowtitle.'</th>'.
+                '<td><select name="version">'.
+                '<option>'.&mt('Select').'</option>'.
+                $list.
+                '</select></td>'.
+                &Apache::loncommon::end_data_table_row();
+            unless ($nomenu) {
+                $result .= &Apache::loncommon::start_data_table_row().
+                '<th align="left">'.&mt('Open in new window').'</th>'.
+                '<td><span class="LC_nobreak">'.
+                '<label><input type="radio" name="prevwin" value="1" />'.
+                &mt('Yes').'</label>'.
+                '<label><input type="radio" name="prevwin" value="0" checked="checked" />'.&mt('No').'</label>'.
+                '</span></td>'.
+                &Apache::loncommon::end_data_table_row();
+            }
+            $result .=
+                &Apache::loncommon::start_data_table_row().
+                '<th align="left">&nbsp;</th>'.
+                '<td>'.
+                '<input type="submit" name="prevsub" value="'.&mt('Display').'" />'.
+                '</td>'.
+                &Apache::loncommon::end_data_table_row().
+                &Apache::loncommon::end_data_table().
+                '</form>';
+            $js = &previous_display_javascript($nomenu,$current);
+        } elsif ($displayed && $nomenu) {
+            $result .= '<a href="javascript:window.close()">'.&mt('Close window').'</a>';
+        } else {
+            $result .= &mt('No previous versions to show for this student');
+        }
+        $result .= '</div>';
+    }
+    return ($current,$displayed,$result,$js);
+}
+
+sub previous_display_javascript {
+    my ($nomenu,$current) = @_;
+    my $js = <<"JSONE";
+<script type="text/javascript">
+// <![CDATA[
+function previousVersion(uname,udom,symb) {
+    var current = '$current';
+    var version = document.getprev.version.options[document.getprev.version.selectedIndex].value;
+    var prevstr = new RegExp("^\\\\d+\$");
+    if (!prevstr.test(version)) {
+        return false;
+    }
+    var url = '';
+    if (version == current) {
+        url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=submission';
+    } else {
+        url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=versionsub&previousversion='+version;
+    }
+JSONE
+    if ($nomenu) {
+        $js .= <<"JSTWO";
+    document.location.href = url;
+JSTWO
+    } else {
+        $js .= <<"JSTHREE";
+    var newwin = 0;
+    for (var i=0; i<document.getprev.prevwin.length; i++) {
+        if (document.getprev.prevwin[i].checked == true) {
+            newwin = document.getprev.prevwin[i].value;
+        }
+    }
+    if (newwin == 1) {
+        var options = 'height=600,width=800,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no';
+        url = url+'&inhibitmenu=yes';
+        if (typeof(previousWin) == 'undefined' || previousWin.closed) {
+            previousWin = window.open(url,'',options,1);
+        } else {
+            previousWin.location.href = url;
+        }
+        previousWin.focus();
+        return false;
+    } else {
+        document.location.href = url;
+        return false;
+    }
+JSTHREE
+    }
+    $js .= <<"ENDJS";
+    return false;
+}
+// ]]>
+</script>
+ENDJS
+
+}
+
 #--- Called from submission routine
 sub processHandGrade {
-    my ($request) = shift;
-    my $symb   = &get_symb($request);
+    my ($request,$symb) = @_;
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     my $button = $env{'form.gradeOpt'};
     my $ngrade = $env{'form.NCT'};
@@ -2425,16 +2995,26 @@ sub processHandGrade {
 	my $ctr = 0;
 	while ($ctr < $ngrade) {
 	    my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
-	    my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr);
+	    my ($errorflag,$pts,$wgt,$numhidden) = 
+                &saveHandGrade($request,$symb,$uname,$udom,$ctr);
 	    if ($errorflag eq 'no_score') {
 		$ctr++;
 		next;
 	    }
 	    if ($errorflag eq 'not_allowed') {
-		$request->print("<span class=\"LC_warning\">Not allowed to modify grades for $uname:$udom</span>");
+		$request->print(
+                    '<span class="LC_error">'
+                   .&mt('Not allowed to modify grades for [_1]',"$uname:$udom")
+                   .'</span>');
 		$ctr++;
 		next;
 	    }
+            if ($numhidden) {
+                $request->print(
+                    '<span class="LC_info">'
+                   .&mt('For [_1]: [quant,_2,transaction] hidden',"$uname:$udom",$numhidden)
+                   .'</span><br />');
+            }
 	    my $includemsg = $env{'form.includemsg'.$ctr};
 	    my ($subject,$message,$msgstatus) = ('','','');
 	    my $restitle = &Apache::lonnet::gettitle($symb);
@@ -2453,7 +3033,7 @@ sub processHandGrade {
 		if ($env{'form.withgrades'.$ctr}) {
 		    $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
 		    $messagetail = " for <a href=\"".
-		                   $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
+		                   $feedurl."?symb=$showsymb\">$restitle</a>";
 		}
 		$msgstatus = 
                     &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
@@ -2462,7 +3042,7 @@ sub processHandGrade {
                                                      undef,undef,$showsymb,
                                                      $restitle);
 		$request->print('<br />'.&mt('Sending message to [_1]',$uname.':'.$udom).': '.
-				$msgstatus);
+				$msgstatus.'<br />');
 	    }
 	    if ($env{'form.collaborator'.$ctr}) {
 		my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
@@ -2481,7 +3061,7 @@ sub processHandGrade {
 						      $udom);
 			    if ($env{'form.withgrades'.$ctr}) {
 				$messagetail = " for <a href=\"".
-                                    $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
+                                    $baseurl."?symb=$showsymb\">$restitle</a>";
 			    }
 			    $msgstatus = 
 				&Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
@@ -2493,7 +3073,8 @@ sub processHandGrade {
 	}
     }
 
-    if ($env{'form.handgrade'} eq 'yes') {
+#    if ($env{'form.handgrade'} eq 'yes') {
+    if (1) {
 	# Keywords sorted in alphabatical order
 	my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
 	my %keyhash = ();
@@ -2546,22 +3127,12 @@ sub processHandGrade {
 	    my $processUser = $env{'form.unamedom'.$ctr};
 	    ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
 	    $env{'form.fullname'} = $$fullname{$processUser};
-	    &submission($request,$ctr,$total-1);
+	    &submission($request,$ctr,$total-1,$symb);
 	    $ctr++;
 	}
 	return '';
     }
 
-# Go directly to grade student - from submission or link from chart page
-    if ($button eq 'Grade Student') {
-	(undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($symb);
-	my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};
-	($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
-	$env{'form.fullname'} = $$fullname{$processUser};
-	&submission($request,0,0);
-	return '';
-    }
-
     # Get the next/previous one or group of students
     my $firststu = $env{'form.unamedom0'};
     my $laststu = $env{'form.unamedom'.($ngrade-1)};
@@ -2581,6 +3152,7 @@ sub processHandGrade {
 		 }
 		 return $a cmp $b;
 	     } (keys(%$fullname))) {
+# FIXME: this is fishy, looks like the button label
 	if ($nextflg == 1 && $button =~ /Next$/) {
 	    push(@parsedlist,$item);
 	}
@@ -2591,8 +3163,14 @@ sub processHandGrade {
 	}
     }
     $ctr = 0;
+# FIXME: this is fishy, looks like the button label
     @parsedlist = reverse @parsedlist if ($button eq 'Previous');
-    my ($partlist) = &response_type($symb);
+    my $res_error;
+    my ($partlist) = &response_type($symb,\$res_error);
+    if ($res_error) {
+        $request->print(&navmap_errormsg());
+        return;
+    }
     foreach my $student (@parsedlist) {
 	my $submitonly=$env{'form.submitonly'};
 	my ($uname,$udom) = split(/:/,$student);
@@ -2638,14 +3216,11 @@ sub processHandGrade {
 	$env{'form.student'}  = $uname;
 	$env{'form.userdom'}  = $udom;
 	$env{'form.fullname'} = $$fullname{$_};
-	&submission($request,$ctr,$total);
+	&submission($request,$ctr,$total,$symb);
 	$ctr++;
     }
     if ($total < 0) {
-	my $the_end = '<h3><span class="LC_info">'.&mt('LON-CAPA User Message').'</span></h3><br />'."\n";
-	$the_end.=&mt('<b>Message: </b> No more students for this section or class.').'<br /><br />'."\n";
-	$the_end.=&mt('Click on the button below to return to the grading menu.').'<br /><br />'."\n";
-	$the_end.=&show_grading_menu_form($symb);
+	my $the_end.='<p>'.&mt('[_1]Message:[_2] No more students for this section or class.','<b>','</b>').'</p>'."\n";
 	$request->print($the_end);
     }
     return '';
@@ -2661,9 +3236,14 @@ sub saveHandGrade {
     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
     my @parts_graded;
     my %newrecord  = ();
-    my ($pts,$wgt) = ('','');
+    my ($pts,$wgt,$totchg) = ('','',0);
     my %aggregate = ();
     my $aggregateflag = 0;
+    if ($env{'form.HIDE'.$newflg}) {
+        my ($version,$parts) = split(/:/,$env{'form.HIDE'.$newflg},2);
+        my $numchgs = &makehidden($version,$parts,\%record,$symb,$domain,$stuname,1);
+        $totchg += $numchgs;
+    }
     my @parts = split(/:/,$env{'form.partlist'.$newflg});
     foreach my $new_part (@parts) {
 	#collaborator ($submi may vary for different parts
@@ -2766,7 +3346,37 @@ sub saveHandGrade {
         &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 			      $cdom,$cnum);
     }
-    return ('',$pts,$wgt);
+    return ('',$pts,$wgt,$totchg);
+}
+
+sub makehidden {
+    my ($version,$parts,$record,$symb,$domain,$stuname,$tolog) = @_;
+    return unless (ref($record) eq 'HASH');
+    my %modified;
+    my $numchanged = 0;
+    if (exists($record->{$version.':keys'})) {
+        my $partsregexp = $parts;
+        $partsregexp =~ s/,/|/g;
+        foreach my $key (split(/\:/,$record->{$version.':keys'})) {
+            if ($key =~ /^resource\.(?:$partsregexp)\.([^\.]+)$/) {
+                 my $item = $1;
+                 unless (($item eq 'solved') || ($item =~ /^award(|msg|ed)$/)) {
+                     $modified{$key} = $record->{$version.':'.$key};
+                 }
+            } elsif ($key =~ m{^(resource\.(?:$partsregexp)\.[^\.]+\.)(.+)$}) {
+                $modified{$1.'hidden'.$2} = $record->{$version.':'.$key};
+            } elsif ($key =~ /^(ip|timestamp|host)$/) {
+                $modified{$key} = $record->{$version.':'.$key};
+            }
+        }
+        if (keys(%modified)) {
+            if (&Apache::lonnet::putstore($env{'request.course.id'},$symb,$version,\%modified,
+                                          $domain,$stuname,$tolog) eq 'ok') {
+                $numchanged ++;
+            }
+        }
+    }
+    return $numchanged;
 }
 
 sub check_and_remove_from_queue {
@@ -2790,66 +3400,85 @@ sub check_and_remove_from_queue {
 sub handback_files {
     my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
     my $portfolio_root = '/userfiles/portfolio';
-    my ($partlist,$handgrade,$responseType) = &response_type($symb);
-
+    my $res_error;
+    my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
+    if ($res_error) {
+        $request->print('<br />'.&navmap_errormsg().'<br />');
+        return;
+    }
+    my @handedback;
+    my $file_msg;
     my @part_response_id = &flatten_responseType($responseType);
     foreach my $part_response_id (@part_response_id) {
     	my ($part_id,$resp_id) = @{ $part_response_id };
 	my $part_resp = join('_',@{ $part_response_id });
-            if (($env{'form.'.$newflg.'_'.$part_resp.'_returndoc1'}) && ($new_part == $part_id)) {
-                # if multiple files are uploaded names will be 'returndoc2','returndoc3'
-                my $file_counter = 1;
-		my $file_msg;
-                while ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter}) {
-                    my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'.filename'};
+        if (($env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'} =~ /^\d+$/) & ($new_part eq $part_id)) {
+            for (my $counter=1; $counter<=$env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'}; $counter++) {
+                # if multiple files are uploaded names will be 'returndoc2','returndoc3' 
+                if ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter}) {
+                    my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter.'.filename'};
                     my ($directory,$answer_file) = 
-                        ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter} =~ /^(.*?)([^\/]*)$/);
+                        ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter} =~ /^(.*?)([^\/]*)$/);
                     my ($answer_name,$answer_ver,$answer_ext) =
-		        &file_name_version_ext($answer_file);
+		        &Apache::lonnet::file_name_version_ext($answer_file);
 		    my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
                     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 ($dir_list,$listerror) = 
+                        &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,
+                                                 $domain,$stuname,$getpropath);
+		    my $version = &Apache::lonnet::get_next_version($answer_name,$answer_ext,$dir_list);
+                    # fix filename
                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
                     my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
-            	                                $newflg.'_'.$part_resp.'_returndoc'.$file_counter,
+            	                                $newflg.'_'.$part_resp.'_returndoc'.$counter,
             	                                $save_file_name);
                     if ($result !~ m|^/uploaded/|) {
                         $request->print('<br /><span class="LC_error">'.
                             &mt('An error occurred ([_1]) while trying to upload [_2].',
-                                $result,$newflg.'_'.$part_resp.'_returndoc'.$file_counter).
+                                $result,$newflg.'_'.$part_resp.'_returndoc'.$counter).
                                         '</span>');
                     } else {
                         # mark the file as read only
-                        my @files = ($save_file_name);
-                        my @what = ($symb,$env{'request.course.id'},'handback');
-                        &Apache::lonnet::mark_as_readonly($domain,$stuname,\@files,\@what);
+                        push(@handedback,$save_file_name);
 			if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {
 			    $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
 			}
                         $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;
-			$file_msg.= "\n".'<br /><span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span><br />";
-
+			$file_msg.= '<span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span> <br />";
                     }
-                    $request->print("<br />".$fname." will be the uploaded file name");
-                    $request->print(" ".$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter});
-                    $file_counter++;
-                }
-		my $subject = "File Handed Back by Instructor ";
-		my $message = "A file has been returned that was originally submitted in reponse to: <br />";
-		$message .= "<strong>".&Apache::lonnet::gettitle($symb)."</strong><br />";
-		$message .= ' The returned file(s) are named: '. $file_msg;
-		$message .= " and can be found in your portfolio space.";
-		my ($feedurl,$showsymb) = 
-		    &get_feedurl_and_symb($symb,$domain,$stuname);
-                my $restitle = &Apache::lonnet::gettitle($symb);
-		my $msgstatus = 
-                   &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject.
-			 ' (File Returned) ['.$restitle.']',$message,undef,
-                         $feedurl,undef,undef,undef,$showsymb,$restitle);
+                    $request->print('<br />'.&mt('[_1] will be the uploaded filename [_2]','<span class="LC_info">'.$fname.'</span>','<span class="LC_filename">'.$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter}.'</span>'));
+                }
             }
         }
+    }
+    if (@handedback > 0) {
+        $request->print('<br />');
+        my @what = ($symb,$env{'request.course.id'},'handback');
+        &Apache::lonnet::mark_as_readonly($domain,$stuname,\@handedback,\@what);
+        my $user_lh = &Apache::loncommon::user_lang($stuname,$domain,$env{'request.course.id'});    
+        my ($subject,$message);
+        if (scalar(@handedback) == 1) {
+            $subject = &mt_user($user_lh,'File Handed Back by Instructor');
+            $message = &mt_user($user_lh,'A file has been returned that was originally submitted in response to: ');
+        } else {
+            $subject = &mt_user($user_lh,'Files Handed Back by Instructor');
+            $message = &mt_user($user_lh,'Files have been returned that were originally submitted in response to: ');
+        }
+        $message .= "<p><strong>".&Apache::lonnet::gettitle($symb)." </strong></p>";
+        $message .= &mt_user($user_lh,'The returned file(s) are named: [_1]',"<br />$file_msg <br />").
+                    &mt_user($user_lh,'The file(s) can be found in your [_1]portfolio[_2].','<a href="/adm/portfolio">','</a>');
+        my ($feedurl,$showsymb) =
+            &get_feedurl_and_symb($symb,$domain,$stuname);
+        my $restitle = &Apache::lonnet::gettitle($symb);
+        $subject .= ' '.&mt_user($user_lh,'(File Returned)').' ['.$restitle.']';
+        my $msgstatus =
+             &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject,
+                 $message,undef,$feedurl,undef,undef,undef,$showsymb,
+                 $restitle);
+        if ($msgstatus) {
+            $request->print(&mt('Notification message status: [_1]','<span class="LC_info">'.$msgstatus.'</span>').'<br />');
+        }
+    }
     return;
 }
 
@@ -2946,27 +3575,14 @@ sub version_portfiles {
     my $version_parts = join('|',@$v_flag);
     my @returned_keys;
     my $parts = join('|', @$parts_graded);
-    my $portfolio_root = '/userfiles/portfolio';
     foreach my $key (keys(%$record)) {
         my $new_portfiles;
         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
             my @versioned_portfiles;
             my @portfiles = split(/\s*,\s*/,$$record{$key});
-            foreach my $file (@portfiles) {
-                &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
-                my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
-		my ($answer_name,$answer_ver,$answer_ext) =
-		    &file_name_version_ext($answer_file);
-                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') {
-                    push(@versioned_portfiles, $directory.$new_answer);
-                    &Apache::lonnet::mark_as_readonly($domain,$stu_name,
-                        [$directory.$new_answer],
-                        [$symb,$env{'request.course.id'},'graded']);
-                }
+            if (@portfiles) {
+                &Apache::lonnet::portfiles_versioning($symb,$domain,$stu_name,\@portfiles,
+                                                      \@versioned_portfiles);
             }
             $$record{$key} = join(',',@versioned_portfiles);
             push(@returned_keys,$key);
@@ -2975,61 +3591,6 @@ sub version_portfiles {
     return (@returned_keys);   
 }
 
-sub get_next_version {
-    my ($answer_name, $answer_ext, $dir_list) = @_;
-    my $version;
-    foreach my $row (@$dir_list) {
-        my ($file) = split(/\&/,$row,2);
-        my ($file_name,$file_version,$file_ext) =
-	    &file_name_version_ext($file);
-        if (($file_name eq $answer_name) && 
-	    ($file_ext eq $answer_ext)) {
-                # gets here if filename and extension match, regardless of version
-                if ($file_version ne '') {
-                # a versioned file is found  so save it for later
-                if ($file_version > $version) {
-		    $version = $file_version;
-	        }
-            }
-        }
-    } 
-    $version ++;
-    return($version);
-}
-
-sub version_selected_portfile {
-    my ($domain,$stu_name,$directory,$file_name,$version) = @_;
-    my ($answer_name,$answer_ver,$answer_ext) =
-        &file_name_version_ext($file_name);
-    my $new_answer;
-    $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
-    if($env{'form.copy'} eq '-1') {
-        $new_answer = 'problem getting file';
-    } else {
-        $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
-        my $copy_result = &Apache::lonnet::finishuserfileupload(
-                            $stu_name,$domain,'copy',
-		        '/portfolio'.$directory.$new_answer);
-    }    
-    return ($new_answer);
-}
-
-sub file_name_version_ext {
-    my ($file)=@_;
-    my @file_parts = split(/\./, $file);
-    my ($name,$version,$ext);
-    if (@file_parts > 1) {
-	$ext=pop(@file_parts);
-	if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) {
-	    $version=pop(@file_parts);
-	}
-	$name=join('.',@file_parts);
-    } else {
-	$name=join('.',@file_parts);
-    }
-    return($name,$version,$ext);
-}
-
 #--------------------------------------------------------------------------------------
 #
 #-------------------------- Next few routines handles grading by section or whole class
@@ -3039,8 +3600,8 @@ sub viewgrades_js {
     my ($request) = shift;
 
     my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
-    $request->print(<<VIEWJAVASCRIPT);
-<script type="text/javascript" language="javascript">
+    &js_escape(\$alertmsg);
+    $request->print(&Apache::lonhtmlcommon::scripttag(<<VIEWJAVASCRIPT));
    function writePoint(partid,weight,point) {
 	var radioButton = document.classgrade["RADVAL_"+partid];
 	var textbox = document.classgrade["TEXTVAL_"+partid];
@@ -3201,16 +3762,19 @@ sub viewgrades_js {
 	}
     }
 
-</script>
 VIEWJAVASCRIPT
 }
 
 #--- show scores for a section or whole class w/ option to change/update a score
 sub viewgrades {
-    my ($request) = shift;
+    my ($request,$symb) = @_;
+    my ($is_tool,$toolsymb);
+    if ($symb =~ /ext\.tool$/) {
+        $is_tool = 1;
+        $toolsymb = $symb;
+    }
     &viewgrades_js($request);
 
-    my ($symb) = &get_symb($request);
     #need to make sure we have the correct data for later EXT calls, 
     #thus invalidate the cache
     &Apache::lonnet::devalidatecourseresdata(
@@ -3219,7 +3783,6 @@ sub viewgrades {
     &Apache::lonnet::clear_EXT_cache_status();
 
     my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
-    $result.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
 
     #view individual student submission form - called using Javascript viewOneStudent
     $result.=&jscriptNform($symb);
@@ -3230,36 +3793,97 @@ sub viewgrades {
 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="command" value="editgrades" />'."\n".
 	&build_section_inputs().
-	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
 	'<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
-	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 
-    my ($common_header,$specific_header);
-    if ($env{'form.section'} eq 'all') {
-	$common_header = &mt('Assign Common Grade to Class');
-        $specific_header = &mt('Assign Grade to Specific Students in Class');
-    } elsif ($env{'form.section'} eq 'none') {
-        $common_header = &mt('Assign Common Grade to Students in no Section');
-	$specific_header = &mt('Assign Grade to Specific Students in no Section');
-    } else {
-        my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
-        $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
-	$specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
+    #retrieve selected groups
+    my (@groups,$group_display);
+    @groups = &Apache::loncommon::get_env_multiple('form.group');
+    if (grep(/^all$/,@groups)) {
+        @groups = ('all');
+    } elsif (grep(/^none$/,@groups)) {
+        @groups = ('none');
+    } elsif (@groups > 0) {
+        $group_display = join(', ',@groups);
+    }
+
+    my ($common_header,$specific_header,@sections,$section_display);
+    @sections = &Apache::loncommon::get_env_multiple('form.section');
+    if (grep(/^all$/,@sections)) {
+        @sections = ('all');
+        if ($group_display) {
+            $common_header = &mt('Assign Common Grade to Students in Group(s) [_1]',$group_display);
+            $specific_header = &mt('Assign Grade to Specific Students in Group(s) [_1]',$group_display);
+        } elsif (grep(/^none$/,@groups)) {
+            $common_header = &mt('Assign Common Grade to Students not assigned to any groups');
+            $specific_header = &mt('Assign Grade to Specific Students not assigned to any groups');
+        } else {
+	    $common_header = &mt('Assign Common Grade to Class');
+            $specific_header = &mt('Assign Grade to Specific Students in Class');
+        }
+    } elsif (grep(/^none$/,@sections)) {
+        @sections = ('none');
+        if ($group_display) {
+            $common_header = &mt('Assign Common Grade to Students in no Section and in Group(s) [_1]',$group_display);
+            $specific_header = &mt('Assign Grade to Specific Students in no Section and in Group(s)',$group_display);
+        } elsif (grep(/^none$/,@groups)) {
+            $common_header = &mt('Assign Common Grade to Students in no Section and in no Group');
+            $specific_header = &mt('Assign Grade to Specific Students in no Section and in no Group');
+        } else {
+            $common_header = &mt('Assign Common Grade to Students in no Section');
+	    $specific_header = &mt('Assign Grade to Specific Students in no Section');
+        }
+    } else {
+        $section_display = join (", ",@sections);
+        if ($group_display) {
+            $common_header = &mt('Assign Common Grade to Students in Section(s) [_1], and in Group(s) [_2]',
+                                 $section_display,$group_display);
+            $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1], and in Group(s) [_2]',
+                                   $section_display,$group_display);
+        } elsif (grep(/^none$/,@groups)) {
+            $common_header = &mt('Assign Common Grade to Students in Section(s) [_1] and no Group',$section_display);
+            $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1] and no Group',$section_display);
+        } else {
+            $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
+	    $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
+        }
+    }
+    my %submit_types = &substatus_options();
+    my $submission_status = $submit_types{$env{'form.submitonly'}};
+
+    if ($env{'form.submitonly'} eq 'all') {
+        $result.= '<h3>'.$common_header.'</h3>';
+    } else {
+        my $text;
+        if ($is_tool) {
+            $text = &mt('(transaction status: "[_1]")',$submission_status);
+        } else {
+            $text = &mt('(submission status: "[_1]")',$submission_status);
+        }
+        $result.= '<h3>'.$common_header.'&nbsp;'.$text.'</h3>';
     }
-    $result.= '<h3>'.$common_header.'</h3>'.&Apache::loncommon::start_data_table();
+    $result .= &Apache::loncommon::start_data_table();
     #radio buttons/text box for assigning points for a section or class.
     #handles different parts of a problem
-    my ($partlist,$handgrade,$responseType) = &response_type($symb);
+    my $res_error;
+    my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
+    if ($res_error) {
+        return &navmap_errormsg();
+    }
     my %weight = ();
     my $ctsparts = 0;
     my %seen = ();
-    my @part_response_id = &flatten_responseType($responseType);
+    my @part_response_id;
+    if ($is_tool) {
+        @part_response_id = ([0,'']);
+    } else {
+        @part_response_id = &flatten_responseType($responseType);
+    }
     foreach my $part_response_id (@part_response_id) {
     	my ($partid,$respid) = @{ $part_response_id };
 	my $part_resp = join('_',@{ $part_response_id });
 	next if $seen{$partid};
 	$seen{$partid}++;
-	my $handgrade=$$handgrade{$part_resp};
+#	my $handgrade=$$handgrade{$part_resp};
 	my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
 	$weight{$partid} = $wgt eq '' ? '1' : $wgt;
 
@@ -3275,12 +3899,13 @@ sub viewgrades {
 	}
 	$radio.='</tr></table>';
 	my $line = '<input type="text" name="TEXTVAL_'.
-	    $partid.'" size="4" '.'onChange="javascript:writePoint(\''.
+	    $partid.'" size="4" '.'onchange="javascript:writePoint(\''.
 		$partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
 	    $weight{$partid}.' '.&mt('(problem weight)').'</td>'."\n";
-	$line.= '<td><b>'.&mt('Grade Status').':</b><select name="SELVAL_'.$partid.'"'.
-	    'onChange="javascript:writeRadText(\''.$partid.'\','.
-		$weight{$partid}.')"> '.
+        $line.= '<td><b>'.&mt('Grade Status').':</b>'.
+            '<select name="SELVAL_'.$partid.'" '.
+            'onchange="javascript:writeRadText(\''.$partid.'\','.
+                $weight{$partid}.')"> '.
 	    '<option selected="selected"> </option>'.
 	    '<option value="excused">'.&mt('excused').'</option>'.
 	    '<option value="reset status">'.&mt('reset status').'</option>'.
@@ -3293,37 +3918,54 @@ sub viewgrades {
 
 	$result.=
 	    &Apache::loncommon::start_data_table_row()."\n".
-	    '<td><b>'.&mt('Part').':</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points').':</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'.
+	    '<td><b>'.&mt('Part:').'</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points:').'</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'.
 	    &Apache::loncommon::end_data_table_row()."\n";
 	$ctsparts++;
     }
     $result.=&Apache::loncommon::end_data_table()."\n".
 	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
     $result.='<input type="button" value="'.&mt('Revert to Default').'" '.
-	'onClick="javascript:resetEntry('.$ctsparts.');" />';
+	'onclick="javascript:resetEntry('.$ctsparts.');" />';
 
     #table listing all the students in a section/class
     #header of table
-    $result.= '<h3>'.$specific_header.'</h3>'.
-              &Apache::loncommon::start_data_table().
+    if ($env{'form.submitonly'} eq 'all') {
+        $result.= '<h3>'.$specific_header.'</h3>';
+    } else {
+        my $text;
+        if ($is_tool) {
+            $text = &mt('(transaction status: "[_1]")',$submission_status);
+        } else {
+            $text = &mt('(submission status: "[_1]")',$submission_status);
+        }
+        $result.= '<h3>'.$specific_header.'&nbsp;'.$text.'</h3>';
+    }
+    $result.= &Apache::loncommon::start_data_table().
 	      &Apache::loncommon::start_data_table_header_row().
 	      '<th>'.&mt('No.').'</th>'.
 	      '<th>'.&nameUserString('header')."</th>\n";
-    my (@parts) = sort(&getpartlist($symb));
+    my $partserror;
+    my (@parts) = sort(&getpartlist($symb,\$partserror));
+    if ($partserror) {
+        return &navmap_errormsg();
+    }
     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
     my @partids = ();
     foreach my $part (@parts) {
-	my $display=&Apache::lonnet::metadata($url,$part.'.display');
+	my $display=&Apache::lonnet::metadata($url,$part.'.display',$toolsymb);
         my $narrowtext = &mt('Tries');
 	$display =~ s|^Number of Attempts|$narrowtext <br />|; # makes the column narrower
-	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
+	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name',$toolsymb); }
 	my ($partid) = &split_part_type($part);
         push(@partids,$partid);
+#
+# FIXME: Looks like $display looks at English text
+#
 	my $display_part=&get_display_part($partid,$symb);
 	if ($display =~ /^Partial Credit Factor/) {
 	    $result.='<th>'.
-		&mt('Score Part: [_1]<br /> (weight = [_2])',
-		    $display_part,$weight{$partid}).'</th>'."\n";
+		&mt('Score Part: [_1][_2](weight = [_3])',
+		    $display_part,'<br />',$weight{$partid}).'</th>'."\n";
 	    next;
 	    
 	} else {
@@ -3344,7 +3986,7 @@ sub viewgrades {
 
     #get info for each student
     #list all the students - with points and grade status
-    my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
+    my (undef,undef,$fullname) = &getclasslist(\@sections,'1',\@groups);
     my $ctr = 0;
     foreach (sort 
 	     {
@@ -3353,36 +3995,142 @@ sub viewgrades {
 		 }
 		 return $a cmp $b;
 	     } (keys(%$fullname))) {
-	$ctr++;
 	$result.=&viewstudentgrade($symb,$env{'request.course.id'},
-				   $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);
+				   $_,$$fullname{$_},\@parts,\%weight,\$ctr,\%last_resets,$is_tool);
     }
     $result.=&Apache::loncommon::end_data_table();
     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
     $result.='<input type="button" value="'.&mt('Save').'" '.
-	'onClick="javascript:submit();" target="_self" /></form>'."\n";
-    if (scalar(%$fullname) eq 0) {
-	my $colspan=3+scalar(@parts);
-	my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
+	'onclick="javascript:submit();" target="_self" /></form>'."\n";
+    if ($ctr == 0) {
         my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
-	$result='<span class="LC_warning">'.
-	    &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
-	        $section_display, $stu_status).
-	    '</span>';
+        $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>'.
+                '<span class="LC_warning">';
+        if ($env{'form.submitonly'} eq 'all') {
+            if (grep(/^all$/,@sections)) {
+                if (grep(/^all$/,@groups)) {
+                    $result .= &mt('There are no students with enrollment status [_1] to modify or grade.',
+                                   $stu_status);
+                } elsif (grep(/^none$/,@groups)) {
+                    $result .= &mt('There are no students with no group assigned and with enrollment status [_1] to modify or grade.',
+                                   $stu_status); 
+                } else {
+                    $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] to modify or grade.',
+                                   $group_display,$stu_status);
+                }
+            } elsif (grep(/^none$/,@sections)) {
+                if (grep(/^all$/,@groups)) {
+                    $result .= &mt('There are no students in no section with enrollment status [_1] to modify or grade.',
+                                   $stu_status);
+                } elsif (grep(/^none$/,@groups)) {
+                    $result .= &mt('There are no students in no section and no group with enrollment status [_1] to modify or grade.',
+                                   $stu_status);
+                } else {
+                    $result .= &mt('There are no students in no section in group(s) [_1] with enrollment status [_2] to modify or grade.',
+                                   $group_display,$stu_status);
+                }
+            } else {
+                if (grep(/^all$/,@groups)) {
+                    $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
+                                   $section_display,$stu_status);
+                } elsif (grep(/^none$/,@groups)) {
+                    $result .= &mt('There are no students in section(s) [_1] and no group with enrollment status [_2] to modify or grade.',
+                                   $section_display,$stu_status);
+                } else {
+                    $result .= &mt('There are no students in section(s) [_1] and group(s) [_2] with enrollment status [_3] to modify or grade.',
+                                   $section_display,$group_display,$stu_status);
+                }
+            }
+        } else {
+            if (grep(/^all$/,@sections)) {
+                if (grep(/^all$/,@groups)) {
+                    $result .= &mt('There are no students with enrollment status [_1] and submission status "[_2]" to modify or grade.',
+                                   $stu_status,$submission_status);
+                } elsif (grep(/^none$/,@groups)) {
+                    $result .= &mt('There are no students with no group assigned with enrollment status [_1] and submission status "[_2]" to modify or grade.',
+                                   $stu_status,$submission_status);
+                } else {
+                    $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
+                                   $group_display,$stu_status,$submission_status);
+                }
+            } elsif (grep(/^none$/,@sections)) {
+                if (grep(/^all$/,@groups)) {
+                    $result .= &mt('There are no students in no section with enrollment status [_1] and submission status "[_2]" to modify or grade.',
+                                   $stu_status,$submission_status);
+                } elsif (grep(/^none$/,@groups)) {
+                    $result .= &mt('There are no students in no section and no group with enrollment status [_1] and submission status "[_2]" to modify or grade.',
+                                   $stu_status,$submission_status);
+                } else {
+                    $result .= &mt('There are no students in no section in group(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
+                                   $group_display,$stu_status,$submission_status);
+                }
+            } else {
+                if (grep(/^all$/,@groups)) {
+	            $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
+	                           $section_display,$stu_status,$submission_status);
+                } elsif (grep(/^none$/,@groups)) {
+                    $result .= &mt('There are no students in section(s) [_1] and no group with enrollment status [_2] and submission status "[_3]" to modify or grade.',
+                                   $section_display,$stu_status,$submission_status);
+                } else {
+                    $result .= &mt('There are no students in section(s) [_1] and group(s) [_2] with enrollment status [_3] and submission status "[_4]" to modify or grade.',
+                                   $section_display,$group_display,$stu_status,$submission_status);
+                }
+            }
+        }
+	$result .= '</span><br />';
     }
-    $result.=&show_grading_menu_form($symb);
     return $result;
 }
 
-#--- call by previous routine to display each student
+#--- call by previous routine to display each student who satisfies submission filter. 
 sub viewstudentgrade {
-    my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
+    my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets,$is_tool) = @_;
     my ($uname,$udom) = split(/:/,$student);
     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
-    my %aggregates = (); 
+    my $submitonly = $env{'form.submitonly'};
+    unless (($submitonly eq 'all') || ($submitonly eq 'queued')) {
+        my %partstatus = ();
+        if (ref($parts) eq 'ARRAY') {
+            foreach my $apart (@{$parts}) {
+                my ($part,$type) = &split_part_type($apart);
+                my ($status,undef) = split(/_/,$record{"resource.$part.solved"},2);
+                $status = 'nothing' if ($status eq '');
+                $partstatus{$part}      = $status;
+                my $subkey = "resource.$part.submitted_by";
+                $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
+            }
+            my $submitted = 0;
+            my $graded = 0;
+            my $incorrect = 0;
+            foreach my $key (keys(%partstatus)) {
+                $submitted = 1 if ($partstatus{$key} ne 'nothing');
+                $graded = 1 if ($partstatus{$key} =~ /^ungraded/);
+                $incorrect = 1 if ($partstatus{$key} =~ /^incorrect/);
+
+                my $partid = (split(/\./,$key))[1];
+                if ($partstatus{'resource.'.$partid.'.'.$key.'.submitted_by'} ne '') {
+                    $submitted = 0;
+                }
+            }
+            return if (!$submitted && ($submitonly eq 'yes' ||
+                                       $submitonly eq 'incorrect' ||
+                                       $submitonly eq 'graded'));
+            return if (!$graded && ($submitonly eq 'graded'));
+            return if (!$incorrect && $submitonly eq 'incorrect');
+        }
+    }
+    if ($submitonly eq 'queued') {
+        my ($cdom,$cnum) = split(/_/,$courseid);
+        my %queue_status =
+            &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
+                                                    $udom,$uname);
+        return if (!defined($queue_status{'gradingqueue'}));
+    }
+    $$ctr++;
+    my %aggregates = ();
     my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'.
-	'<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.
-	"\n".$ctr.'&nbsp;</td><td>&nbsp;'.
+	'<input type="hidden" name="ctr'.($$ctr-1).'" value="'.$student.'" />'.
+	"\n".$$ctr.'&nbsp;</td><td>&nbsp;'.
 	'<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
 	'\');" target="_self">'.$fullname.'</a> '.
 	'<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
@@ -3394,7 +4142,6 @@ sub viewstudentgrade {
         my ($aggtries,$totaltries);
         unless (exists($aggregates{$part})) {
 	    $totaltries = $record{'resource.'.$part.'.tries'};
-
 	    $aggtries = $totaltries;
             if ($$last_resets{$part}) {  
                 $aggtries = &get_num_tries(\%record,$$last_resets{$part},
@@ -3412,7 +4159,7 @@ sub viewstudentgrade {
 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
 	    $result.='<input type="text" name="'.
 		'GD_'.$student.'_'.$part.'_awarded" '.
-		'onChange="javascript:changeSelect(\''.$part.'\',\''.$student.
+                'onchange="javascript:changeSelect(\''.$part.'\',\''.$student.
 		'\')" value="'.$pts.'" size="4" /></td>'."\n";
 	} elsif ($type eq 'solved') {
 	    my ($status,$foo)=split(/_/,$score,2);
@@ -3421,7 +4168,7 @@ sub viewstudentgrade {
 		$part.'_solved_s" value="'.$status.'" />'."\n";
 	    $result.='&nbsp;<select name="'.
 		'GD_'.$student.'_'.$part.'_solved" '.
-		'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
+                'onchange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
 	    $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
 		: '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
 	    $result.='<option value="reset status">'.&mt('reset status').'</option>';
@@ -3442,12 +4189,14 @@ sub viewstudentgrade {
 #--- change scores for all the students in a section/class
 #    record does not get update if unchanged
 sub editgrades {
-    my ($request) = @_;
+    my ($request,$symb) = @_;
+    my $toolsymb;
+    if ($symb =~ /ext\.tool$/) {
+        $toolsymb = $symb;
+    }
 
-    my $symb=&get_symb($request);
     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     my $title='<h2>'.&mt('Current Grade Status').'</h2>';
-    $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
     $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
 
     my $result= &Apache::loncommon::start_data_table().
@@ -3459,6 +4208,7 @@ sub editgrades {
 		    'incorrect'=>'incorrect_by_override',
 		    'excused'  =>'excused',
 		    'ungraded' =>'ungraded_attempted',
+                    'credited' =>'credit_attempted',
 		    'nothing'  => '',
 		    );
     my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
@@ -3468,7 +4218,11 @@ sub editgrades {
     my %columns = ();
     my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
 
-    my (@parts) = sort(&getpartlist($symb));
+    my $partserror;
+    my (@parts) = sort(&getpartlist($symb,\$partserror));
+    if ($partserror) {
+        return &navmap_errormsg();
+    }
     my $header;
     while ($ctr < $env{'form.totalparts'}) {
 	my $partid = $env{'form.partid_'.$ctr};
@@ -3477,6 +4231,7 @@ sub editgrades {
 	$ctr++;
     }
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
+    my $totcolspan = 0;
     foreach my $partid (@partid) {
 	$header .= '<th align="center">'.&mt('Old Score').'</th>'.
 	    '<th align="center">'.&mt('New Score').'</th>';
@@ -3485,7 +4240,7 @@ sub editgrades {
 	    my ($part,$type) = &split_part_type($stores);
 	    if ($part !~ m/^\Q$partid\E/) { next;}
 	    if ($type eq 'awarded' || $type eq 'solved') { next; }
-	    my $display=&Apache::lonnet::metadata($url,$stores.'.display');
+	    my $display=&Apache::lonnet::metadata($url,$stores.'.display',$toolsymb);
 	    $display =~ s/\[Part: \Q$part\E\]//;
             my $narrowtext = &mt('Tries');
 	    $display =~ s/Number of Attempts/$narrowtext/;
@@ -3493,6 +4248,7 @@ sub editgrades {
 		'<th align="center">'.&mt('New').' '.$display.'</th>';
 	    $columns{$partid}+=2;
 	}
+        $totcolspan += $columns{$partid};
     }
     foreach my $partid (@partid) {
 	my $display_part=&get_display_part($partid,$symb);
@@ -3508,18 +4264,18 @@ sub editgrades {
     my @noupdate;
     my ($updateCtr,$noupdateCtr) = (1,1);
     for ($i=0; $i<$env{'form.total'}; $i++) {
-	my $line;
 	my $user = $env{'form.ctr'.$i};
 	my ($uname,$udom)=split(/:/,$user);
 	my %newrecord;
 	my $updateflag = 0;
-	$line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
 	my $usec=$classlist->{"$uname:$udom"}[5];
-	if (!&canmodify($usec)) {
-	    my $numcols=scalar(@partid)*4+2;
+	my $canmodify = &canmodify($usec);
+	my $line = '<td'.($canmodify?'':' colspan="2"').'>'.
+		   &nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
+	if (!$canmodify) {
 	    push(@noupdate,
-		 $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">".
-		 &mt('Not allowed to modify student')."</span></td></tr>");
+		 $line."<td colspan=\"$totcolspan\"><span class=\"LC_warning\">".
+		 &mt('Not allowed to modify student')."</span></td>");
 	    next;
 	}
         my %aggregate = ();
@@ -3636,8 +4392,7 @@ sub editgrades {
         }
     }
     if (@noupdate) {
-#	my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
-	my $numcols=scalar(@partid)*4+2;
+        my $numcols=$totcolspan+2;
 	$result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
 	    '<td align="center" colspan="'.$numcols.'">'.
 	    &mt('No Changes Occurred For the Students Below').
@@ -3650,8 +4405,7 @@ sub editgrades {
 		&Apache::loncommon::end_data_table_row();
 	}
     }
-    $result .= &Apache::loncommon::end_data_table().
-	&show_grading_menu_form($symb);
+    $result .= &Apache::loncommon::end_data_table();
     my $msg = '<p><b>'.
 	&mt('Number of records updated = [_1] for [quant,_2,student].',
 	    $rec_update,$count).'</b><br />'.
@@ -3679,20 +4433,24 @@ sub split_part_type {
 #
 #--- Javascript to handle csv upload
 sub csvupload_javascript_reverse_associate {
-    my $error1=&mt('You need to specify the username or the student/employee ID');
+    my $error1=&mt('You need to specify the username, the student/employee ID, or the clicker ID');
     my $error2=&mt('You need to specify at least one grading field');
+  &js_escape(\$error1);
+  &js_escape(\$error2);
   return(<<ENDPICK);
   function verify(vf) {
     var foundsomething=0;
     var founduname=0;
     var foundID=0;
+    var foundclicker=0;
     for (i=0;i<=vf.nfields.value;i++) {
       tw=eval('vf.f'+i+'.selectedIndex');
       if (i==0 && tw!=0) { foundID=1; }
       if (i==1 && tw!=0) { founduname=1; }
-      if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }
+      if (i==2 && tw!=0) { foundclicker=1; }
+      if (i!=0 && i!=1 && i!=2 && i!=3 && tw!=0) { foundsomething=1; }
     }
-    if (founduname==0 && foundID==0) {
+    if (founduname==0 && foundID==0 && foundclicker==0) {
 	alert('$error1');
 	return;
     }
@@ -3719,20 +4477,24 @@ ENDPICK
 }
 
 sub csvupload_javascript_forward_associate {
-    my $error1=&mt('You need to specify the username or the student/employee ID');
+    my $error1=&mt('You need to specify the username, the student/employee ID, or the clicker ID');
     my $error2=&mt('You need to specify at least one grading field');
+  &js_escape(\$error1);
+  &js_escape(\$error2);
   return(<<ENDPICK);
   function verify(vf) {
     var foundsomething=0;
     var founduname=0;
     var foundID=0;
+    var foundclicker=0;
     for (i=0;i<=vf.nfields.value;i++) {
       tw=eval('vf.f'+i+'.selectedIndex');
       if (tw==1) { foundID=1; }
       if (tw==2) { founduname=1; }
-      if (tw>3) { foundsomething=1; }
+      if (tw==3) { foundclicker=1; }
+      if (tw>4) { foundsomething=1; }
     }
-    if (founduname==0 && foundID==0) {
+    if (founduname==0 && foundID==0 && Æ’oundclicker==0) {
 	alert('$error1');
 	return;
     }
@@ -3764,21 +4526,14 @@ sub csvuploadmap_header {
 	$javascript=&csvupload_javascript_forward_associate();
     }
 
-    my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
-    my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
-    my $ignore=&mt('Ignore First Line');
     $symb = &Apache::lonenc::check_encrypt($symb);
+    $request->print('<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">'.
+                    &mt('Total number of records found in file: [_1]',$distotal).'<hr />'.
+                    &mt('Associate entries from the uploaded file with as many fields as you can.'));
+    my $reverse=&mt("Reverse Association");
     $request->print(<<ENDPICK);
-<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
-<h3><span class="LC_info">Uploading Class Grades</span></h3>
-$result
-<hr />
-<h3>Identify fields</h3>
-Total number of records found in file: $distotal <hr />
-Enter as many fields as you can. The system will inform you and bring you back
-to this page if the data selected is insufficient to run your class.<hr />
-<input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
-<label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>
+<br />
+<input type="button" value="$reverse" onclick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
 <input type="hidden" name="associate"  value="" />
 <input type="hidden" name="phase"      value="three" />
 <input type="hidden" name="datatoken"  value="$datatoken" />
@@ -3787,30 +4542,37 @@ to this page if the data selected is ins
 <input type="hidden" name="upfile_associate" 
                                        value="$env{'form.upfile_associate'}" />
 <input type="hidden" name="symb"       value="$symb" />
-<input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
-<input type="hidden" name="probTitle"  value="$env{'form.probTitle'}" />
 <input type="hidden" name="command"    value="csvuploadoptions" />
 <hr />
-<script type="text/javascript" language="Javascript">
-$javascript
-</script>
 ENDPICK
+    $request->print(&Apache::lonhtmlcommon::scripttag($javascript));
     return '';
 
 }
 
 sub csvupload_fields {
-    my ($symb) = @_;
-    my (@parts) = &getpartlist($symb);
+    my ($symb,$errorref) = @_;
+    my $toolsymb;
+    if ($symb =~ /ext\.tool$/) {
+        $toolsymb = $symb;
+    }
+    my (@parts) = &getpartlist($symb,$errorref);
+    if (ref($errorref)) {
+        if ($$errorref) {
+            return;
+        }
+    }
+
     my @fields=(['ID','Student/Employee ID'],
 		['username','Student Username'],
+		['clicker','Clicker ID'],
 		['domain','Student Domain']);
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     foreach my $part (sort(@parts)) {
 	my @datum;
-	my $display=&Apache::lonnet::metadata($url,$part.'.display');
+	my $display=&Apache::lonnet::metadata($url,$part.'.display',$toolsymb);
 	my $name=$part;
-	if  (!$display) { $display = $name; }
+	if (!$display) { $display = $name; }
 	@datum=($name,$display);
 	if ($name=~/^stores_(.*)_awarded/) {
 	    push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
@@ -3822,19 +4584,20 @@ sub csvupload_fields {
 
 sub csvuploadmap_footer {
     my ($request,$i,$keyfields) =@_;
+    my $buttontext = &mt('Assign Grades');
     $request->print(<<ENDPICK);
 </table>
 <input type="hidden" name="nfields" value="$i" />
 <input type="hidden" name="keyfields" value="$keyfields" />
-<input type="button" onClick="javascript:verify(this.form)" value="Assign Grades" /><br />
+<input type="button" onclick="javascript:verify(this.form)" value="$buttontext" /><br />
 </form>
 ENDPICK
 }
 
 sub checkforfile_js {
     my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
-    my $result =<<CSVFORMJS;
-<script type="text/javascript" language="javascript">
+    &js_escape(\$alertmsg);
+    my $result = &Apache::lonhtmlcommon::scripttag(<<CSVFORMJS);
     function checkUpload(formname) {
 	if (formname.upfile.value == "") {
 	    alert("$alertmsg");
@@ -3842,24 +4605,19 @@ sub checkforfile_js {
 	}
 	formname.submit();
     }
-    </script>
 CSVFORMJS
     return $result;
 }
 
 sub upcsvScores_form {
-    my ($request) = shift;
-    my ($symb)=&get_symb($request);
+    my ($request,$symb) = @_;
     if (!$symb) {return '';}
     my $result=&checkforfile_js();
-    $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
-    my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
-    $result.=$table;
-    $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
-    $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
-    $result.='&nbsp;<b>'.&mt('Specify a file containing the class scores for current resource.').
-	'</b></td></tr>'."\n";
-    $result.='<tr bgcolor=#ffffe6><td>'."\n";
+    $result.=&Apache::loncommon::start_data_table().
+             &Apache::loncommon::start_data_table_header_row().
+             '<th>'.&mt('Specify a file containing the class scores for current resource.').'</th>'.
+             &Apache::loncommon::end_data_table_header_row().
+             &Apache::loncommon::start_data_table_row().'<td>';
     my $upload=&mt("Upload Scores");
     my $upfile_select=&Apache::loncommon::upfile_select_html();
     my $ignore=&mt('Ignore First Line');
@@ -3868,41 +4626,42 @@ sub upcsvScores_form {
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <input type="hidden" name="symb" value="$symb" />
 <input type="hidden" name="command" value="csvuploadmap" />
-<input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
-<input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 $upfile_select
-<br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
-<label><input type="checkbox" name="noFirstLine" />$ignore</label>
+<br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
 </form>
 ENDUPFORM
     $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
-                           &mt("How do I create a CSV file from a spreadsheet"))
-    .'</td></tr></table>'."\n";
-    $result.='</td></tr></table><br /><br />'."\n";
-    $result.=&show_grading_menu_form($symb);
+                           &mt("How do I create a CSV file from a spreadsheet")).
+             '</td>'.
+            &Apache::loncommon::end_data_table_row().
+            &Apache::loncommon::end_data_table();
     return $result;
 }
 
 
 sub csvuploadmap {
-    my ($request)= @_;
-    my ($symb)=&get_symb($request);
+    my ($request,$symb)= @_;
     if (!$symb) {return '';}
 
     my $datatoken;
     if (!$env{'form.datatoken'}) {
 	$datatoken=&Apache::loncommon::upfile_store($request);
     } else {
-	$datatoken=$env{'form.datatoken'};
-	&Apache::loncommon::load_tmp_file($request);
+	$datatoken=&Apache::loncommon::valid_datatoken($env{'form.datatoken'});
+        if ($datatoken ne '') {
+	    &Apache::loncommon::load_tmp_file($request,$datatoken);
+        }
     }
     my @records=&Apache::loncommon::upfile_record_sep();
-    if ($env{'form.noFirstLine'}) { shift(@records); }
     &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
     my ($i,$keyfields);
     if (@records) {
-	my @fields=&csvupload_fields($symb);
-
+        my $fieldserror;
+	my @fields=&csvupload_fields($symb,\$fieldserror);
+        if ($fieldserror) {
+            $request->print(&navmap_errormsg());
+            return;
+        }
 	if ($env{'form.upfile_associate'} eq 'reverse') {	
 	    &Apache::loncommon::csv_print_samples($request,\@records);
 	    $i=&Apache::loncommon::csv_print_select_table($request,\@records,
@@ -3923,39 +4682,27 @@ sub csvuploadmap {
 	}
     }
     &csvuploadmap_footer($request,$i,$keyfields);
-    $request->print(&show_grading_menu_form($symb));
 
     return '';
 }
 
 sub csvuploadoptions {
-    my ($request)= @_;
-    my ($symb)=&get_symb($request);
-    my $checked=(($env{'form.noFirstLine'})?'1':'0');
-    my $ignore=&mt('Ignore First Line');
+    my ($request,$symb)= @_;
+    my $overwrite=&mt('Overwrite any existing score');
     $request->print(<<ENDPICK);
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
-<h3><span class="LC_info">Uploading Class Grade Options</span></h3>
 <input type="hidden" name="command"    value="csvuploadassign" />
-<!--
-<p>
-<label>
-   <input type="checkbox" name="show_full_results" />
-   Show a table of all changes
-</label>
-</p>
--->
 <p>
 <label>
    <input type="checkbox" name="overwite_scores" checked="checked" />
-   Overwrite any existing score
+   $overwrite
 </label>
 </p>
 ENDPICK
     my %fields=&get_fields();
     if (!defined($fields{'domain'})) {
 	my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
-	$request->print("\n<p> Users are in domain: ".$domform."</p>\n");
+	$request->print("\n<p>".&mt('Users are in domain: [_1]',$domform)."</p>\n");
     }
     foreach my $key (sort(keys(%env))) {
 	if ($key !~ /^form\.(.*)$/) { next; }
@@ -3966,9 +4713,8 @@ ENDPICK
     }
     # FIXME do a check for any duplicated user ids...
     # FIXME do a check for any invalid user ids?...
-    $request->print('<input type="submit" value="Assign Grades" /><br />
+    $request->print('<input type="submit" value="'.&mt('Assign Grades').'" /><br />
 <hr /></form>'."\n");
-    $request->print(&show_grading_menu_form($symb));
     return '';
 }
 
@@ -3990,19 +4736,20 @@ sub get_fields {
 }
 
 sub csvuploadassign {
-    my ($request)= @_;
-    my ($symb)=&get_symb($request);
+    my ($request,$symb)= @_;
     if (!$symb) {return '';}
     my $error_msg = '';
-    &Apache::loncommon::load_tmp_file($request);
+    my $datatoken = &Apache::loncommon::valid_datatoken($env{'form.datatoken'});
+    if ($datatoken ne '') { 
+        &Apache::loncommon::load_tmp_file($request,$datatoken);
+    }
     my @gradedata = &Apache::loncommon::upfile_record_sep();
-    if ($env{'form.noFirstLine'}) { shift(@gradedata); }
     my %fields=&get_fields();
-    $request->print('<h3>Assigning Grades</h3>');
     my $courseid=$env{'request.course.id'};
     my ($classlist) = &getclasslist('all',0);
     my @notallowed;
     my @skipped;
+    my @warnings;
     my $countdone=0;
     foreach my $grade (@gradedata) {
 	my %entries=&Apache::loncommon::record_sep($grade);
@@ -4018,13 +4765,45 @@ sub csvuploadassign {
 	if (!$username) {
 	    my $id=$entries{$fields{'ID'}};
 	    $id=~s/\s//g;
-	    my %ids=&Apache::lonnet::idget($domain,$id);
-	    $username=$ids{$id};
+            if ($id ne '') {
+	        my %ids=&Apache::lonnet::idget($domain,[$id]);
+	        $username=$ids{$id};
+            } else {
+                if ($entries{$fields{'clicker'}}) {
+                    my $clicker = $entries{$fields{'clicker'}};
+                    $clicker=~s/\s//g;
+                    if ($clicker ne '') {
+                        my %clickers = &Apache::lonnet::idget($domain,[$clicker],'clickers');
+                        if ($clickers{$clicker} ne '') {  
+                            my $match = 0;
+                            my @inclass;
+                            foreach my $poss (split(/,/,$clickers{$clicker})) {
+                                if (exists($$classlist{"$poss:$domain"})) {
+                                    $username = $poss;
+                                    push(@inclass,$poss);
+                                    $match ++;
+                                    
+                                }
+                            }
+                            if ($match > 1) {
+                                undef($username); 
+                                $request->print('<p class="LC_warning">'.
+                                                &mt('Score not saved for clicker: [_1] (matched multiple usernames: [_2])',
+                                                $clicker,join(', ',@inclass)).'</p>');
+                            }
+                        }
+                    }
+                }
+            }
 	}
 	if (!exists($$classlist{"$username:$domain"})) {
 	    my $id=$entries{$fields{'ID'}};
 	    $id=~s/\s//g;
-	    if ($id) {
+            my $clicker = $entries{$fields{'clicker'}};
+            $clicker=~s/\s//g;
+            if ($clicker) {
+                push(@skipped,"$clicker:$domain");
+	    } elsif ($id) {
 		push(@skipped,"$id:$domain");
 	    } else {
 		push(@skipped,"$username:$domain");
@@ -4051,6 +4830,9 @@ sub csvuploadassign {
                     my $pcr=$entries{$fields{$dest}} / $wgt;
                     my $award=($pcr == 0) ? 'incorrect_by_override'
                                           : 'correct_by_override';
+                    if ($pcr>1) {
+                       push(@warnings,&mt("[_1]: point value larger than weight","$username:$domain"));
+                    }
                     $grades{"resource.$part.awarded"}=$pcr;
                     $grades{"resource.$part.solved"}=$award;
                     $points{$part}=1;
@@ -4078,17 +4860,27 @@ sub csvuploadassign {
 					   $env{'request.course.id'},
 					   $domain,$username);
 	   if ($result eq 'ok') {
+# Successfully stored
 	      $request->print('.');
-	   } else {
+# Remove from grading queue
+              &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,
+                                             $env{'course.'.$env{'request.course.id'}.'.domain'},
+                                             $env{'course.'.$env{'request.course.id'}.'.num'},
+                                             $domain,$username);
+              $countdone++;
+           } else {
 	      $request->print("<p><span class=\"LC_error\">".
                               &mt("Failed to save data for student [_1]. Message when trying to save was: [_2]",
                                   "$username:$domain",$result)."</span></p>");
 	   }
 	   $request->rflush();
-	   $countdone++;
         }
     }
     $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
+    if (@warnings) {
+        $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Warnings generated for the following saved scores:'),1).'<br />');
+        $request->print(join(', ',@warnings));
+    }
     if (@skipped) {
 	$request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('No scores stored for the following username(s):'),1).'<br />');
         $request->print(join(', ',@skipped));
@@ -4098,7 +4890,6 @@ sub csvuploadassign {
 	$request->print(join(', ',@notallowed));
     }
     $request->print("<br />\n");
-    $request->print(&show_grading_menu_form($symb));
     return $error_msg;
 }
 #------------- end of section for handling csv file upload ---------
@@ -4109,11 +4900,11 @@ sub csvuploadassign {
 #
 #--- Select a page/sequence and a student to grade
 sub pickStudentPage {
-    my ($request) = shift;
+    my ($request,$symb) = @_;
 
     my $alertmsg = &mt('Please select the student you wish to grade.');
-    $request->print(<<LISTJAVASCRIPT);
-<script type="text/javascript" language="javascript">
+    &js_escape(\$alertmsg);
+    $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
 
 function checkPickOne(formname) {
     if (radioSelection(formname.student) == null) {
@@ -4126,10 +4917,9 @@ function checkPickOne(formname) {
     formname.submit();
 }
 
-</script>
 LISTJAVASCRIPT
     &commonJSfunctions($request);
-    my ($symb) = &get_symb($request);
+
     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
@@ -4138,54 +4928,78 @@ LISTJAVASCRIPT
 	&mt('Manual Grading by Page or Sequence').'</span></h3>';
 
     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
-    my ($titles,$symbx) = &getSymbMap();
+    my $map_error;
+    my ($titles,$symbx) = &getSymbMap($map_error);
+    if ($map_error) {
+        $request->print(&navmap_errormsg());
+        return; 
+    }
     my ($curpage) =&Apache::lonnet::decode_symb($symb); 
 #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
 #    my $type=($curpage =~ /\.(page|sequence)/);
-    my $select = '<select name="selectpage">'."\n";
-    my $ctr=0;
-    foreach (@$titles) {
-	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
-	$select.='<option value="'.$ctr.'" '.
-	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
-	    '>'.$showtitle.'</option>'."\n";
-	$ctr++;
-    }
-    $select.= '</select>';
-    $result.='&nbsp;<b>'.&mt('Problems from').':</b> '.$select."<br />\n";
 
-    $ctr=0;
+    # Collection of hidden fields
+    my $ctr=0;
     foreach (@$titles) {
-	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
-	$result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
-	$result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
-	$ctr++;
+        my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
+        $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
+        $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
+        $ctr++;
     }
     $result.='<input type="hidden" name="page" />'."\n".
-	'<input type="hidden" name="title" />'."\n";
+        '<input type="hidden" name="title" />'."\n";
 
-    my $options =
-	'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
-	'<label><input type="radio" name="vProb" value="yes" /> '.&mt('yes').' </label>'."<br />\n";
-    $result.='&nbsp;<b>'.&mt('View Problem Text').': </b>'.$options;
-
-    $options =
-	'<label><input type="radio" name="lastSub" value="none" /> '.&mt('none').' </label>'."\n".
-	'<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.&mt('by dates and submissions').'</label>'."\n".
-	'<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";
-    $result.='&nbsp;<b>'.&mt('Submissions').': </b>'.$options;
-    
     $result.=&build_section_inputs();
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     $result.='<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
 	'<input type="hidden" name="command" value="displayPage" />'."\n".
-	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
-	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
 
-    $result.='&nbsp;<b>'.&mt('Use CODE').': </b> <input type="text" name="CODE" value="" /> <br />'."\n";
+    # Show grading options
+    $result.=&Apache::lonhtmlcommon::start_pick_box();
+    my $select = '<select name="selectpage">'."\n";
+    $ctr=0;
+    foreach (@$titles) {
+	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
+	$select.='<option value="'.$ctr.'"'.
+	    ($$symbx{$_} =~ /$curpage$/ ? ' selected="selected"' : '').
+	    '>'.$showtitle.'</option>'."\n";
+	$ctr++;
+    }
+    $select.= '</select>';
 
-    $result.='&nbsp;<input type="button" '.
-	'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /><br />'."\n";
+    $result.=
+        &Apache::lonhtmlcommon::row_title(&mt('Problems from'))
+       .$select
+       .&Apache::lonhtmlcommon::row_closure();
+
+    $result.=
+        &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
+       .'<label><input type="radio" name="vProb" value="no"'
+           .' checked="checked" /> '.&mt('no').' </label>'."\n"
+       .'<label><input type="radio" name="vProb" value="yes" />'
+           .&mt('yes').'</label>'."\n"
+       .&Apache::lonhtmlcommon::row_closure();
+
+    $result.=
+        &Apache::lonhtmlcommon::row_title(&mt('View Submissions'))
+       .'<label><input type="radio" name="lastSub" value="none" /> '
+           .&mt('none').' </label>'."\n"
+       .'<label><input type="radio" name="lastSub" value="datesub"'
+           .' checked="checked" /> '.&mt('all submissions').'</label>'."\n"
+       .'<label><input type="radio" name="lastSub" value="all" /> '
+           .&mt('all submissions with details').' </label>'
+       .&Apache::lonhtmlcommon::row_closure();
+    
+    $result.=
+        &Apache::lonhtmlcommon::row_title(&mt('Use CODE'))
+       .'<input type="text" name="CODE" value="" />'
+       .&Apache::lonhtmlcommon::row_closure(1)
+       .&Apache::lonhtmlcommon::end_pick_box();
+
+    # Show list of students to select for grading
+    $result.='<br /><input type="button" '.
+             'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /><br />'."\n";
 
     $request->print($result);
 
@@ -4224,17 +5038,22 @@ LISTJAVASCRIPT
     }
     $studentTable.=&Apache::loncommon::end_data_table()."\n";
     $studentTable.='<input type="button" '.
-	'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /></form>'."\n";
+                   'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /></form>'."\n";
 
-    $studentTable.=&show_grading_menu_form($symb);
     $request->print($studentTable);
 
     return '';
 }
 
 sub getSymbMap {
+    my ($map_error) = @_;
     my $navmap = Apache::lonnavmaps::navmap->new();
-
+    unless (ref($navmap)) {
+        if (ref($map_error)) {
+            $$map_error = 'navmap';
+        }
+        return;
+    }
     my %symbx = ();
     my @titles = ();
     my $minder = 0;
@@ -4243,7 +5062,7 @@ sub getSymbMap {
     my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
 					       1,0,1);
     for my $sequence ($navmap->getById('0.0'), @sequences) {
-	if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
+	if ($navmap->hasResource($sequence, sub { shift->is_gradable(); }, 0) ) {
 	    my $title = $minder.'.'.
 		&HTML::Entities::encode($sequence->compTitle(),'"\'&');
 	    push(@titles, $title); # minder in case two titles are identical
@@ -4257,9 +5076,7 @@ sub getSymbMap {
 #
 #--- Displays a page/sequence w/wo problems, w/wo submissions
 sub displayPage {
-    my ($request) = shift;
-
-    my ($symb) = &get_symb($request);
+    my ($request,$symb) = @_;
     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
@@ -4276,9 +5093,12 @@ sub displayPage {
     &Apache::lonnet::clear_EXT_cache_status();
 
     if (!&canview($usec)) {
-	$request->print('<span class="LC_warning">'.&mt('Unable to view requested student. ([_1])',$env{'form.student'}).'</span>');
-	$request->print(&show_grading_menu_form($symb));
-	return;
+        $request->print(
+            '<span class="LC_warning">'.
+            &mt('Unable to view requested student. ([_1])',
+                    $env{'form.student'}).
+            '</span>');
+        return;
     }
     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
     $result.='<h3>&nbsp;'.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
@@ -4293,11 +5113,14 @@ sub displayPage {
     $request->print($result);
 
     my $navmap = Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        $request->print(&navmap_errormsg());
+        return;
+    }
     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
     if (!$map) {
 	$request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
-	$request->print(&show_grading_menu_form($symb));
 	return; 
     }
     my $iterator = $navmap->getIterator($map->map_start(),
@@ -4310,8 +5133,7 @@ sub displayPage {
 	'<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
 	'<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
-	'<input type="hidden" name="overRideScore" value="no" />'."\n".
-	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
+	'<input type="hidden" name="overRideScore" value="no" />'."\n";
 
     if (defined($env{'form.CODE'})) {
 	$studentTable.=
@@ -4320,10 +5142,12 @@ sub displayPage {
     my $checkIcon = '<img alt="'.&mt('Check Mark').
 	'" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
 
-    $studentTable.='&nbsp;'.&mt('<b>Note:</b> Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon)."\n".
+    $studentTable.='&nbsp;<span class="LC_info">'.
+        &mt('Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon).
+        '</span>'."\n".
 	&Apache::loncommon::start_data_table().
 	&Apache::loncommon::start_data_table_header_row().
-	'<th align="center">&nbsp;Prob.&nbsp;</th>'.
+	'<th>'.&mt('Prob.').'</th>'.
 	'<th>&nbsp;'.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').'</th>'.
 	&Apache::loncommon::end_data_table_header_row();
 
@@ -4335,40 +5159,49 @@ sub displayPage {
         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
         if($curRes == $iterator->END_MAP) { $depth--; }
 
-        if (ref($curRes) && $curRes->is_problem()) {
+        if (ref($curRes) && $curRes->is_gradable()) {
 	    my $parts = $curRes->parts();
             my $title = $curRes->compTitle();
 	    my $symbx = $curRes->symb();
+            my $is_tool = ($symbx =~ /ext\.tool$/);
 	    $studentTable.=
 		&Apache::loncommon::start_data_table_row().
 		'<td align="center" valign="top" >'.$prob.
 		(scalar(@{$parts}) == 1 ? '' 
-		                        : '<br />('.&mt('[_1]&nbsp;parts)',
-							scalar(@{$parts}))
+		                        : '<br />('.&mt('[_1]parts',
+							scalar(@{$parts}).'&nbsp;').')'
 		 ).
 		 '</td>';
 	    $studentTable.='<td valign="top">';
 	    my %form = ('CODE' => $env{'form.CODE'},);
-	    if ($env{'form.vProb'} eq 'yes' ) {
-		$studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
-					     undef,'both',\%form);
-	    } else {
-		my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
-		$companswer =~ s|<form(.*?)>||g;
-		$companswer =~ s|</form>||g;
-#		while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
-#		    $companswer =~ s/$1/ /ms;
-#		    $request->print('match='.$1."<br />\n");
-#		}
-#		$companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
-		$studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>'.&mt('Correct answer').':</b><br />'.$companswer;
+            if ($is_tool) {
+                $studentTable.='&nbsp;<b>'.$title.'</b><br />';
+            } else {
+	        if ($env{'form.vProb'} eq 'yes' ) {
+		    $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
+					         undef,'both',\%form);
+	        } else {
+		    my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
+		    $companswer =~ s|<form(.*?)>||g;
+		    $companswer =~ s|</form>||g;
+#		    while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
+#		        $companswer =~ s/$1/ /ms;
+#		        $request->print('match='.$1."<br />\n");
+#		    }
+#		    $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
+		    $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>'.&mt('Correct answer').':</b><br />'.$companswer;
+		}
 	    }
 
 	    my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
 
 	    if ($env{'form.lastSub'} eq 'datesub') {
 		if ($record{'version'} eq '') {
-		    $studentTable.='<br />&nbsp;<span class="LC_warning">'.&mt('No recorded submission for this problem.').'</span><br />';
+                    my $msg = &mt('No recorded submission for this problem.');
+                    if ($is_tool) {
+                        $msg = &mt('No recorded transactions for this external tool');
+                    }
+		    $studentTable.='<br />&nbsp;<span class="LC_warning">'.$msg.'</span><br />';
 		} else {
 		    my %responseType = ();
 		    foreach my $partid (@{$parts}) {
@@ -4381,21 +5214,24 @@ sub displayPage {
 			$responseType{$partid} = \%responseIds;
 		    }
 		    $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
-
 		}
 	    } elsif ($env{'form.lastSub'} eq 'all') {
 		my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
+                my $identifier = (&canmodify($usec)? $prob : ''); 
 		$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
 									$env{'request.course.id'},
-									'','.submission');
+									'','.submission',undef,
+                                                                        $usec,$identifier);
  
 	    }
 	    if (&canmodify($usec)) {
+            $studentTable.=&gradeBox_start();
 		foreach my $partid (@{$parts}) {
 		    $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
 		    $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
 		    $question++;
 		}
+            $studentTable.=&gradeBox_end();
 		$prob++;
 	    }
 	    $studentTable.='</td></tr>';
@@ -4404,11 +5240,11 @@ sub displayPage {
         $curRes = $iterator->next();
     }
 
-    $studentTable.='</table>'."\n".
-	'<input type="button" value="'.&mt('Save').'" '.
-	'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
-	'</form>'."\n";
-    $studentTable.=&show_grading_menu_form($symb);
+    $studentTable.=
+        '</table>'."\n".
+        '<input type="button" value="'.&mt('Save').'" '.
+        'onclick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
+        '</form>'."\n";
     $request->print($studentTable);
 
     return '';
@@ -4418,12 +5254,14 @@ sub displaySubByDates {
     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
     my $isCODE=0;
     my $isTask = ($symb =~/\.task$/);
+    my $is_tool = ($symb =~/\.tool$/);
     if (exists($record->{'resource.CODE'})) { $isCODE=1; }
     my $studentTable=&Apache::loncommon::start_data_table().
 	&Apache::loncommon::start_data_table_header_row().
 	'<th>'.&mt('Date/Time').'</th>'.
 	($isCODE?'<th>'.&mt('CODE').'</th>':'').
-	'<th>'.&mt('Submission').'</th>'.
+        ($isTask?'<th>'.&mt('Version').'</th>':'').
+	'<th>'.($is_tool?&mt('Grade'):&mt('Submission')).'</th>'.
 	'<th>'.&mt('Status').'</th>'.
 	&Apache::loncommon::end_data_table_header_row();
     my ($version);
@@ -4431,18 +5269,25 @@ sub displaySubByDates {
     my %orders;
     $mark{'correct_by_student'} = $checkIcon;
     if (!exists($$record{'1:timestamp'})) {
-	return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />';
+        if ($is_tool) {
+            return '<br />&nbsp;<span class="LC_warning">'.&mt('No grade passed back.').'</span><br />';
+        } else {
+            return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />';
+        }
     }
 
     my $interaction;
     my $no_increment = 1;
+    my (%lastrndseed,%lasttype);
     for ($version=1;$version<=$$record{'version'};$version++) {
 	my $timestamp = 
 	    &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
 	if (exists($$record{$version.':resource.0.version'})) {
 	    $interaction = $$record{$version.':resource.0.version'};
 	}
-
+        if ($isTask && $env{'form.previousversion'}) {
+            next unless ($interaction == $env{'form.previousversion'});
+        }
 	my $where = ($isTask ? "$version:resource.$interaction"
 		             : "$version:resource");
 	$studentTable.=&Apache::loncommon::start_data_table_row().
@@ -4450,40 +5295,76 @@ sub displaySubByDates {
 	if ($isCODE) {
 	    $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
 	}
+        if ($isTask) {
+            $studentTable.='<td>'.$interaction.'</td>';
+        }
 	my @versionKeys = split(/\:/,$$record{$version.':keys'});
 	my @displaySub = ();
 	foreach my $partid (@{$parts}) {
-	    my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
-			            : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
-	    
-
+            my ($hidden,$type);
+            $type = $$record{$version.':resource.'.$partid.'.type'};
+            if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
+                $hidden = 1;
+            }
+            my @matchKey;
+            if ($isTask) {
+                @matchKey = sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys);
+            } elsif ($is_tool) {
+                @matchKey = sort(grep /^resource\.\Q$partid\E\.awarded$/,@versionKeys);
+            } else {
+                @matchKey = sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys);
+            }
 #	    next if ($$record{"$version:resource.$partid.solved"} eq '');
 	    my $display_part=&get_display_part($partid,$symb);
 	    foreach my $matchKey (@matchKey) {
 		if (exists($$record{$version.':'.$matchKey}) &&
 		    $$record{$version.':'.$matchKey} ne '') {
-
-		    my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
-				               : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
-		    $displaySub[0].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.'&nbsp;';
-		    $displaySub[0].='<span class="LC_internal_info">('.&mt('ID').'&nbsp;'.
-			$responseId.')</span>&nbsp;<b>';
-		    if ($$record{"$where.$partid.tries"} eq '') {
-			$displaySub[0].=&mt('Trial&nbsp;not&nbsp;counted');
-		    } else {
-			$displaySub[0].=&mt('Trial&nbsp;[_1]',
-					    $$record{"$where.$partid.tries"});
-		    }
-		    my $responseType=($isTask ? 'Task'
+                    if ($is_tool) {
+                        $displaySub[0].=$$record{"$version:resource.$partid.awarded"};
+                    } else {
+		        my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
+				                   : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
+                        $displaySub[0].='<span class="LC_nobreak">';
+                        $displaySub[0].='<b>'.&mt('Part: [_1]',$display_part).'</b>'
+                                       .' <span class="LC_internal_info">'
+                                       .'('.&mt('Response ID: [_1]',$responseId).')'
+                                       .'</span>'
+                                       .' <b>';
+                        if ($hidden) {
+                            $displaySub[0].= &mt('Anonymous Survey').'</b>';
+                        } else {
+                            my ($trial,$rndseed,$newvariation);
+                            if ($type eq 'randomizetry') {
+                                $trial = $$record{"$where.$partid.tries"};
+                                $rndseed = $$record{"$where.$partid.rndseed"};
+                            }
+		            if ($$record{"$where.$partid.tries"} eq '') {
+			        $displaySub[0].=&mt('Trial not counted');
+		            } else {
+			        $displaySub[0].=&mt('Trial: [_1]',
+					        $$record{"$where.$partid.tries"});
+                                if (($rndseed ne '') && ($lastrndseed{$partid} ne '')) {
+                                    if (($rndseed ne $lastrndseed{$partid}) &&
+                                        (($type eq 'randomizetry') || ($lasttype{$partid} eq 'randomizetry'))) {
+                                        $newvariation = '&nbsp;('.&mt('New variation this try').')';
+                                    }
+                                }
+                                $lastrndseed{$partid} = $rndseed;
+                                $lasttype{$partid} = $type;
+		            }
+		            my $responseType=($isTask ? 'Task'
                                               : $responseType->{$partid}->{$responseId});
-		    if (!exists($orders{$partid})) { $orders{$partid}={}; }
-		    if (!exists($orders{$partid}->{$responseId})) {
-			$orders{$partid}->{$responseId}=
-			    &get_order($partid,$responseId,$symb,$uname,$udom,
-                                       $no_increment);
-		    }
-		    $displaySub[0].='</b>&nbsp; '.
-			&cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />';
+		            if (!exists($orders{$partid})) { $orders{$partid}={}; }
+		            if ((!exists($orders{$partid}->{$responseId})) || ($trial)) {
+			        $orders{$partid}->{$responseId}=
+			            &get_order($partid,$responseId,$symb,$uname,$udom,
+                                               $no_increment,$type,$trial,$rndseed);
+		            }
+		            $displaySub[0].='</b>'.$newvariation.'</span>'; # /nobreak
+		            $displaySub[0].='&nbsp; '.
+			        &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom,$type,$trial,$rndseed).'<br />';
+                        }
+                    }
 		}
 	    }
 	    if (exists($$record{"$where.$partid.checkedin"})) {
@@ -4497,14 +5378,22 @@ sub displaySubByDates {
 		    lc($$record{"$where.$partid.award"}).' '.
 		    $mark{$$record{"$where.$partid.solved"}}.
 		    '<br />';
+	    } elsif (($is_tool) && (exists($$record{"$version:resource.$partid.solved"}))) {
+		if ($$record{"$version:resource.$partid.solved"} =~ /^(in|)correct_by_passback$/) {
+		    $displaySub[1].=&mt('Grade passed back by external tool');
+		}
 	    }
 	    if (exists $$record{"$where.$partid.regrader"}) {
-		$displaySub[2].=$$record{"$where.$partid.regrader"}.
-		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
+		$displaySub[2].=$$record{"$where.$partid.regrader"};
+		unless ($is_tool) {
+		    $displaySub[2].=' (<b>'.&mt('Part').':</b> '.$display_part.')';
+		}
 	    } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
 		$displaySub[2].=
-		    $$record{"$version:resource.$partid.regrader"}.
-		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
+		    $$record{"$version:resource.$partid.regrader"};
+                unless ($is_tool) {
+		    $displaySub[2].=' (<b>'.&mt('Part').':</b> '.$display_part.')';
+                }
 	    }
 	}
 	# needed because old essay regrader has not parts info
@@ -4523,7 +5412,7 @@ sub displaySubByDates {
 }
 
 sub updateGradeByPage {
-    my ($request) = shift;
+    my ($request,$symb) = @_;
 
     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
@@ -4534,7 +5423,6 @@ sub updateGradeByPage {
     my $usec=$classlist->{$env{'form.student'}}[5];
     if (!&canmodify($usec)) {
 	$request->print('<span class="LC_warning">'.&mt('Unable to modify requested student ([_1])',$env{'form.student'}).'</span>');
-	$request->print(&show_grading_menu_form($env{'form.symb'}));
 	return;
     }
     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
@@ -4543,13 +5431,16 @@ sub updateGradeByPage {
 
     $request->print($result);
 
+
     my $navmap = Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        $request->print(&navmap_errormsg());
+        return;
+    }
     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
     if (!$map) {
 	$request->print('<span class="LC_warning">'.&mt('Unable to grade requested sequence ([_1]).',$resUrl).'</span>');
-	my ($symb)=&get_symb($request);
-	$request->print(&show_grading_menu_form($symb));
 	return; 
     }
     my $iterator = $navmap->getIterator($map->map_start(),
@@ -4566,7 +5457,7 @@ sub updateGradeByPage {
 
     $iterator->next(); # skip the first BEGIN_MAP
     my $curRes = $iterator->next(); # for "current resource"
-    my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
+    my ($depth,$question,$prob,$changeflag,$hideflag)= (1,1,1,0,0);
     while ($depth > 0) {
         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
         if($curRes == $iterator->END_MAP) { $depth--; }
@@ -4579,7 +5470,7 @@ sub updateGradeByPage {
 		&Apache::loncommon::start_data_table_row().
 		'<td align="center" valign="top" >'.$prob.
 		(scalar(@{$parts}) == 1 ? '' 
-                                        : '<br />('.&mt('[quant,_1,&nbsp;part]',scalar(@{$parts}))
+                                        : '<br />('.&mt('[quant,_1,part]',scalar(@{$parts}))
 		.')').'</td>';
 	    $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
 
@@ -4587,6 +5478,12 @@ sub updateGradeByPage {
 	    my @displayPts=();
             my %aggregate = ();
             my $aggregateflag = 0;
+            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);
+                my $numchgs = &makehidden($version,$parts,\%record,$symbx,$udom,$uname,1);
+                $hideflag += $numchgs;
+            }
 	    foreach my $partid (@{$parts}) {
 		my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
 		my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
@@ -4675,11 +5572,13 @@ sub updateGradeByPage {
     }
 
     $studentTable.=&Apache::loncommon::end_data_table();
-    $studentTable.=&show_grading_menu_form($env{'form.symb'});
     my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
 		  &mt('The scores were changed for [quant,_1,problem].',
-		  $changeflag));
-    $request->print($grademsg.$studentTable);
+		  $changeflag).'<br />');
+    my $hidemsg=($hideflag == 0 ? '' :
+                 &mt('Submissions were marked "hidden" for [quant,_1,transaction].',
+                     $hideflag).'<br />');
+    $request->print($hidemsg.$grademsg.$studentTable);
 
     return '';
 }
@@ -4688,7 +5587,7 @@ sub updateGradeByPage {
 #
 #-------------------------------------------------------------------
 
-#--------------------Bubblesheet (Scantron) Grading-----------------------------------
+#-------------------- Bubblesheet (Scantron) Grading -------------------
 #
 #------ start of section for handling grading by page/sequence ---------
 
@@ -4702,19 +5601,19 @@ sub updateGradeByPage {
    from the file that we are parsing that represents one entire sheet
 
    'bubble line' refers to the data
-   representing the line of bubbles that are on the physical bubble sheet
+   representing the line of bubbles that are on the physical bubblesheet
 
 
-The overall process is that a scanned in bubble sheet data is uploaded
+The overall process is that a scanned in bubblesheet data is uploaded
 into a course. When a user wants to grade, they select a
-sequence/folder of resources, a file of bubble sheet info, and pick
+sequence/folder of resources, a file of bubblesheet info, and pick
 one of the predefined configurations for what each scanline looks
 like.
 
 Next each scanline is checked for any errors of either 'missing
 bubbles' (it's an error because it may have been mis-scanned
 because too light bubbling), 'double bubble' (each bubble line should
-have no more that one letter picked), invalid or duplicated CODE,
+have no more than one letter picked), invalid or duplicated CODE,
 invalid student/employee ID
 
 If the CODE option is used that determines the randomization of the
@@ -4723,14 +5622,14 @@ username:domain.
 
 During the validation phase the instructor can choose to skip scanlines. 
 
-After the validation phase, there are now 3 bubble sheet files
+After the validation phase, there are now 3 bubblesheet files
 
   scantron_original_filename (unmodified original file)
   scantron_corrected_filename (file where the corrected information has replaced the original information)
   scantron_skipped_filename (contains the exact text of scanlines that where skipped)
 
 Also there is a separate hash nohist_scantrondata that contains extra
-correction information that isn't representable in the bubble sheet
+correction information that isn't representable in the bubblesheet
 file (see &scantron_getfile() for more information)
 
 After all scanlines are either valid, marked as valid or skipped, then
@@ -4753,9 +5652,7 @@ the homework problem.
 
 sub defaultFormData {
     my ($symb)=@_;
-    return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
-     '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
-     '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
+    return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />';
 }
 
 
@@ -4766,14 +5663,19 @@ sub defaultFormData {
    Return html dropdown of possible sequences to grade
  
  Arguments:
-   $symb - $symb of the current resource 
+   $symb - $symb of the current resource
+   $map_error - ref to scalar which will container error if
+                $navmap object is unavailable in &getSymbMap().
 
 =cut
 
 sub getSequenceDropDown {
-    my ($symb)=@_;
+    my ($symb,$map_error)=@_;
     my $result='<select name="selectpage">'."\n";
-    my ($titles,$symbx) = &getSymbMap();
+    my ($titles,$symbx) = &getSymbMap($map_error);
+    if (ref($map_error)) {
+        return if ($$map_error);
+    }
     my ($curpage)=&Apache::lonnet::decode_symb($symb); 
     my $ctr=0;
     foreach (@$titles) {
@@ -4799,6 +5701,11 @@ my %subdivided_bubble_lines;       # no.
 
 my %responsetype_per_response;     # responsetype for each response
 
+my %masterseq_id_responsenum;      # src_id (e.g., 12.3_0.11 etc.) for each
+                                   # numbered response. Needed when randomorder
+                                   # or randompick are in use. Key is ID, value 
+                                   # is response number.
+
 # Save and restore the bubble lines array to the form env.
 
 
@@ -4812,12 +5719,17 @@ sub save_bubble_lines {
         $env{"form.scantron.responsetype.$line"} =
             $responsetype_per_response{$line};
     }
+    foreach my $resid (keys(%masterseq_id_responsenum)) {
+        my $line = $masterseq_id_responsenum{$resid};
+        $env{"form.scantron.residpart.$line"} = $resid;
+    }
 }
 
 
 sub restore_bubble_lines {
     my $line = 0;
     %bubble_lines_per_response = ();
+    %masterseq_id_responsenum = ();
     while ($env{"form.scantron.bubblelines.$line"}) {
 	my $value = $env{"form.scantron.bubblelines.$line"};
 	$bubble_lines_per_response{$line} = $value;
@@ -4827,28 +5739,12 @@ sub restore_bubble_lines {
             $env{"form.scantron.sub_bubblelines.$line"};
         $responsetype_per_response{$line} =
             $env{"form.scantron.responsetype.$line"};
+        my $id = $env{"form.scantron.residpart.$line"};
+        $masterseq_id_responsenum{$id} = $line;
 	$line++;
     }
 }
 
-#  Given the parsed scanline, get the response for 
-#  'answer' number n:
-
-sub get_response_bubbles {
-    my ($parsed_line, $response)  = @_;
-
-    my $bubble_line = $first_bubble_line{$response-1} +1;
-    my $bubble_lines= $bubble_lines_per_response{$response-1};
-    
-    my $selected = "";
-
-    for (my $bline = 0; $bline < $bubble_lines; $bline++) {
-	$selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":";
-	$bubble_line++;
-    }
-    return $selected;
-}
-
 =pod 
 
 =item scantron_filenames
@@ -4861,14 +5757,16 @@ 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,
-                                       $getpropath);
+    my ($dirlist,$listerror) = &Apache::lonnet::dirlist('userfiles',$cdom,
+                                                        $cname,$getpropath);
     my @possiblenames;
-    foreach my $filename (sort(@files)) {
-	($filename)=split(/&/,$filename);
-	if ($filename!~/^scantron_orig_/) { next ; }
-	$filename=~s/^scantron_orig_//;
-	push(@possiblenames,$filename);
+    if (ref($dirlist) eq 'ARRAY') {
+        foreach my $filename (sort(@{$dirlist})) {
+	    ($filename)=split(/&/,$filename);
+	    if ($filename!~/^scantron_orig_/) { next ; }
+	    $filename=~s/^scantron_orig_//;
+	    push(@possiblenames,$filename);
+        }
     }
     return @possiblenames;
 }
@@ -4907,7 +5805,7 @@ sub scantron_uploads {
 sub scantron_scantab {
     my $result='<select name="scantron_format">'."\n";
     $result.='<option></option>'."\n";
-    my @lines = &get_scantronformat_file();
+    my @lines = &Apache::lonnet::get_scantronformat_file();
     if (@lines > 0) {
         foreach my $line (@lines) {
             next if (($line =~ /^\#/) || ($line eq ''));
@@ -4919,62 +5817,6 @@ sub scantron_scantab {
     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
@@ -5022,7 +5864,7 @@ sub scantron_CODEunique {
 
 =item scantron_selectphase
 
-  Generates the initial screen to start the bubble sheet process.
+  Generates the initial screen to start the bubblesheet process.
   Allows for - starting a grading run.
              - downloading existing scan data (original, corrected
                                                 or skipped info)
@@ -5036,12 +5878,15 @@ sub scantron_CODEunique {
 =cut
 
 sub scantron_selectphase {
-    my ($r,$file2grade) = @_;
-    my ($symb)=&get_symb($r);
+    my ($r,$file2grade,$symb) = @_;
     if (!$symb) {return '';}
-    my $sequence_selector=&getSequenceDropDown($symb);
+    my $map_error;
+    my $sequence_selector=&getSequenceDropDown($symb,\$map_error);
+    if ($map_error) {
+        $r->print('<br />'.&navmap_errormsg().'<br />');
+        return;
+    }
     my $default_form_data=&defaultFormData($symb);
-    my $grading_menu_button=&show_grading_menu_form($symb);
     my $file_selector=&scantron_uploads($file2grade);
     my $format_selector=&scantron_scantab();
     my $CODE_selector=&scantron_CODElist();
@@ -5050,6 +5895,64 @@ sub scantron_selectphase {
 
     $ssi_error = 0;
 
+    if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
+        &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
+
+	# Chunk of form to prompt for a scantron file upload.
+
+        $r->print('
+    <br />');
+    my $default_form_data=&defaultFormData($symb);
+    my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
+    my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
+    my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
+    &js_escape(\$alertmsg);
+    my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($cdom);
+    $r->print(&Apache::lonhtmlcommon::scripttag('
+    function checkUpload(formname) {
+	if (formname.upfile.value == "") {
+	    alert("'.$alertmsg.'");
+	    return false;
+	}
+	formname.submit();
+    }'."\n".$formatjs));
+    $r->print('
+              <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
+                '.$default_form_data.'
+                <input name="courseid" type="hidden" value="'.$cnum.'" />
+                <input name="domainid" type="hidden" value="'.$cdom.'" />
+                <input name="command" value="scantronupload_save" type="hidden" />
+              '.&Apache::loncommon::start_data_table('LC_scantron_action').'
+              '.&Apache::loncommon::start_data_table_header_row().'
+                <th>
+                &nbsp;'.&mt('Specify a bubblesheet data file to upload.').'
+                </th>
+              '.&Apache::loncommon::end_data_table_header_row().'
+              '.&Apache::loncommon::start_data_table_row().'
+            <td>
+                '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'<br />'."\n");
+    if ($formatoptions) {
+        $r->print('</td>
+                 '.&Apache::loncommon::end_data_table_row().'
+                 '.&Apache::loncommon::start_data_table_row().'
+                 <td>'.$formattitle.('&nbsp;'x2).$formatoptions.'
+                 </td>
+                 '.&Apache::loncommon::end_data_table_row().'
+                 '.&Apache::loncommon::start_data_table_row().'
+                 <td>'
+        );
+    } else {
+        $r->print(' <br />');
+    }
+    $r->print('<input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
+              </td>
+             '.&Apache::loncommon::end_data_table_row().'
+             '.&Apache::loncommon::end_data_table().'
+             </form>'
+    );
+
+    }
+
     # Chunk of form to prompt for a file to grade and how:
 
     $result.= '
@@ -5097,53 +6000,7 @@ sub scantron_selectphase {
    
     $r->print($result);
 
-    if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
-        &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
-
-	# Chunk of form to prompt for a scantron file upload.
-
-        $r->print('
-    <br />
-    '.&Apache::loncommon::start_data_table('LC_scantron_action').'
-       '.&Apache::loncommon::start_data_table_header_row().'
-            <th>
-              &nbsp;'.&mt('Specify a bubblesheet data file to upload.').'
-            </th>
-       '.&Apache::loncommon::end_data_table_header_row().'
-       '.&Apache::loncommon::start_data_table_row().'
-            <td>
-');
-    my $default_form_data=&defaultFormData(&get_symb($r,1));
-    my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
-    my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
-    $r->print('
-              <script type="text/javascript" language="javascript">
-    function checkUpload(formname) {
-	if (formname.upfile.value == "") {
-	    alert("'.&mt('Please use the browse button to select a file from your local directory.').'");
-	    return false;
-	}
-	formname.submit();
-    }
-              </script>
-
-              <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
-                '.$default_form_data.'
-                <input name="courseid" type="hidden" value="'.$cnum.'" />
-                <input name="domainid" type="hidden" value="'.$cdom.'" />
-                <input name="command" value="scantronupload_save" type="hidden" />
-                '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'
-                <br />
-                <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
-              </form>
-');
 
-        $r->print('
-            </td>
-       '.&Apache::loncommon::end_data_table_row().'
-       '.&Apache::loncommon::end_data_table().'
-');
-    }
 
     # Chunk of the form that prompts to view a scoring office file,
     # corrected file, skipped records in a file.
@@ -5171,7 +6028,7 @@ sub scantron_selectphase {
 
     &Apache::lonpickcode::code_list($r,2);
 
-    $r->print('<br /><form method="post" name="checkscantron">'.
+    $r->print('<br /><form method="post" name="checkscantron" action="">'.
              $default_form_data."\n".
              &Apache::loncommon::start_data_table('LC_scantron_action')."\n".
              &Apache::loncommon::start_data_table_header_row()."\n".
@@ -5203,105 +6060,17 @@ sub scantron_selectphase {
               &Apache::loncommon::end_data_table_row()."\n".
               &Apache::loncommon::end_data_table()."\n".
               '</form><br />');
-    $r->print($grading_menu_button);
     return;
 }
 
-=pod
-
-=item get_scantron_config
-
-   Parse and return the scantron configuration line selected as a
-   hash of configuration file fields.
-
- Arguments:
-    which - the name of the configuration to parse from the file.
-
-
- Returns:
-            If the named configuration is not in the file, an empty
-            hash is returned.
-    a hash with the fields
-      name         - internal name for the this configuration setup
-      description  - text to display to operator that describes this config
-      CODElocation - if 0 or the string 'none'
-                          - no CODE exists for this config
-                     if -1 || the string 'letter'
-                          - a CODE exists for this config and is
-                            a string of letters
-                     Unsupported value (but planned for future support)
-                          if a positive integer
-                               - The CODE exists as the first n items from
-                                 the question section of the form
-                          if the string 'number'
-                               - The CODE exists for this config and is
-                                 a string of numbers
-      CODEstart   - (only matter if a CODE exists) column in the line where
-                     the CODE starts
-      CODElength  - length of the CODE
-      IDstart     - column where the student/employee ID starts
-      IDlength    - length of the student/employee ID info
-      Qstart      - column where the information from the bubbled
-                    'questions' start
-      Qlength     - number of columns comprising a single bubble line from
-                    the sheet. (usually either 1 or 10)
-      Qon         - either a single character representing the character used
-                    to signal a bubble was chosen in the positional setup, or
-                    the string 'letter' if the letter of the chosen bubble is
-                    in the final, or 'number' if a number representing the
-                    chosen bubble is in the file (1->A 0->J)
-      Qoff        - the character used to represent that a bubble was
-                    left blank
-      PaperID     - if the scanning process generates a unique number for each
-                    sheet scanned the column that this ID number starts in
-      PaperIDlength - number of columns that comprise the unique ID number
-                      for the sheet of paper
-      FirstName   - column that the first name starts in
-      FirstNameLength - number of columns that the first name spans
- 
-      LastName    - column that the last name starts in
-      LastNameLength - number of columns that the last name spans
-
-=cut
-
-sub get_scantron_config {
-    my ($which) = @_;
-    my @lines = &get_scantronformat_file();
-    my %config;
-    #FIXME probably should move to XML it has already gotten a bit much now
-    foreach my $line (@lines) {
-	my ($name,$descrip)=split(/:/,$line);
-	if ($name ne $which ) { next; }
-	chomp($line);
-	my @config=split(/:/,$line);
-	$config{'name'}=$config[0];
-	$config{'description'}=$config[1];
-	$config{'CODElocation'}=$config[2];
-	$config{'CODEstart'}=$config[3];
-	$config{'CODElength'}=$config[4];
-	$config{'IDstart'}=$config[5];
-	$config{'IDlength'}=$config[6];
-	$config{'Qstart'}=$config[7];
- 	$config{'Qlength'}=$config[8];
-	$config{'Qoff'}=$config[9];
-	$config{'Qon'}=$config[10];
-	$config{'PaperID'}=$config[11];
-	$config{'PaperIDlength'}=$config[12];
-	$config{'FirstName'}=$config[13];
-	$config{'FirstNamelength'}=$config[14];
-	$config{'LastName'}=$config[15];
-	$config{'LastNamelength'}=$config[16];
-	last;
-    }
-    return %config;
-}
-
 =pod 
 
 =item username_to_idmap
 
     creates a hash keyed by student/employee ID with values of the corresponding
-    student username:domain.
+    student username:domain. If a single ID occurs for more than one student,
+    the status of the student is checked, and if Active, the value in the hash
+    will be set to the Active student.
 
   Arguments:
 
@@ -5319,8 +6088,17 @@ sub username_to_idmap {
     my ($classlist)= @_;
     my %idmap;
     foreach my $student (keys(%$classlist)) {
-	$idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}=
-	    $student;
+        my $id = $classlist->{$student}->[&Apache::loncoursedata::CL_ID];
+        unless ($id eq '') {
+            if (!exists($idmap{$id})) {
+                $idmap{$id} = $student;
+            } else {
+                my $status = $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS];
+                if ($status eq 'Active') {
+                    $idmap{$id} = $student;
+                }
+            }
+        }
     }
     return %idmap;
 }
@@ -5332,7 +6110,7 @@ sub username_to_idmap {
    Process a requested correction to a scanline.
 
   Arguments:
-    $scantron_config   - hash from &get_scantron_config()
+    $scantron_config   - hash from &Apache::lonnet::get_scantron_config()
     $scan_data         - hash of correction information 
                           (see &scantron_getfile())
     $line              - existing scanline
@@ -5492,16 +6270,37 @@ sub digits_to_letters {
 
 =item scantron_parse_scanline
 
-  Decodes a scanline from the selected scantron file
+  Decodes a scanline from the selected bubblesheet file
 
  Arguments:
-    line             - The text of the scantron file line to process
+    line             - The text of the bubblesheet file line to process
     whichline        - Line number
-    scantron_config  - Hash describing the format of the scantron lines.
+    scantron_config  - Hash describing the format of the bubblesheet lines.
     scan_data        - Hash of extra information about the scanline
                        (see scantron_getfile for more information)
     just_header      - True if should not process question answers but only
                        the stuff to the left of the answers.
+    randomorder      - True if randomorder in use
+    randompick       - True if randompick in use
+    sequence         - Exam folder URL
+    master_seq       - Ref to array containing symbs in exam folder
+    symb_to_resource - Ref to hash of symbs for resources in exam folder
+                       (corresponding values are resource objects)
+    partids_by_symb  - Ref to hash of symb -> array ref of partIDs
+    orderedforcode   - Ref to hash of arrays. keys are CODEs and values
+                       are refs to an array of resource objects, ordered
+                       according to order used for CODE, when randomorder
+                       and or randompick are in use.
+    respnumlookup    - Ref to hash mapping question numbers in bubble lines
+                       for current line to question number used for same question
+                        in "Master Sequence" (as seen by Course Coordinator).
+    startline        - Ref to hash where key is question number (0 is first)
+                       and value is number of first bubble line for current 
+                       student or code-based randompick and/or randomorder.
+    totalref         - Ref of scalar used to score total number of bubble
+                       lines needed for responses in a scan line (used when
+                       randompick in use. 
+    
  Returns:
    Hash containing the result of parsing the scanline
 
@@ -5546,12 +6345,12 @@ sub digits_to_letters {
 =cut
 
 sub scantron_parse_scanline {
-    my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
+    my ($line,$whichline,$scantron_config,$scan_data,$just_header,$idmap,
+        $randomorder,$randompick,$sequence,$master_seq,$symb_to_resource,
+        $partids_by_symb,$orderedforcode,$respnumlookup,$startline,$totalref)=@_;
 
     my %record;
-    my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
-    my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos);  # Answers
-    my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
+    my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # stuff before answers
     if (!($$scantron_config{'CODElocation'} eq 0 ||
 	  $$scantron_config{'CODElocation'} eq 'none')) {
 	if ($$scantron_config{'CODElocation'} < 0 ||
@@ -5587,10 +6386,29 @@ sub scantron_parse_scanline {
     my $questnum=0;
     my $ansnum  =1;		# Multiple 'answer lines'/question.
 
+    my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
+    if ($randompick || $randomorder) {
+        my $total = &get_respnum_lookups($sequence,$scan_data,$idmap,$line,\%record,
+                                         $master_seq,$symb_to_resource,
+                                         $partids_by_symb,$orderedforcode,
+                                         $respnumlookup,$startline);
+        if ($total) {
+            $lastpos = $total*$$scantron_config{'Qlength'}; 
+        }
+        if (ref($totalref)) {
+            $$totalref = $total;
+        }
+    }
+    my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos);  # Answers
     chomp($questions);		# Get rid of any trailing \n.
     $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
     while (length($questions)) {
-	my $answers_needed = $bubble_lines_per_response{$questnum};
+        my $answers_needed;
+        if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
+            $answers_needed = $bubble_lines_per_response{$respnumlookup->{$questnum}};
+        } else {
+	    $answers_needed = $bubble_lines_per_response{$questnum};
+        }
         my $answer_length  = ($$scantron_config{'Qlength'} * $answers_needed)
                              || 1;
         $questnum++;
@@ -5599,11 +6417,16 @@ sub scantron_parse_scanline {
         $questions       = substr($questions,$answer_length);
         if (length($currentquest) < $answer_length) { next; }
 
-        if ($subdivided_bubble_lines{$questnum-1} =~ /,/) {
+        my $subdivided;
+        if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
+            $subdivided = $subdivided_bubble_lines{$respnumlookup->{$questnum-1}};
+        } else {
+            $subdivided = $subdivided_bubble_lines{$questnum-1};
+        }
+        if ($subdivided =~ /,/) {
             my $subquestnum = 1;
             my $subquestions = $currentquest;
-            my @subanswers_needed = 
-                split(/,/,$subdivided_bubble_lines{$questnum-1});  
+            my @subanswers_needed = split(/,/,$subdivided);
             foreach my $subans (@subanswers_needed) {
                 my $subans_length =
                     ($$scantron_config{'Qlength'} * $subans)  || 1;
@@ -5614,10 +6437,13 @@ sub scantron_parse_scanline {
                     ($$scantron_config{'Qon'} eq 'number')) {
                     $ansnum = &scantron_validator_lettnum($ansnum, 
                         $questnum,$quest_id,$subans,$currsubquest,$whichline,
-                        \@alphabet,\%record,$scantron_config,$scan_data);
+                        \@alphabet,\%record,$scantron_config,$scan_data,
+                        $randomorder,$randompick,$respnumlookup);
                 } else {
                     $ansnum = &scantron_validator_positional($ansnum,
-                        $questnum,$quest_id,$subans,$currsubquest,$whichline,                        \@alphabet,\%record,$scantron_config,$scan_data);
+                        $questnum,$quest_id,$subans,$currsubquest,$whichline,
+                        \@alphabet,\%record,$scantron_config,$scan_data,
+                        $randomorder,$randompick,$respnumlookup);
                 }
                 $subquestnum ++;
             }
@@ -5626,11 +6452,13 @@ sub scantron_parse_scanline {
                 ($$scantron_config{'Qon'} eq 'number')) {
                 $ansnum = &scantron_validator_lettnum($ansnum,$questnum,
                     $quest_id,$answers_needed,$currentquest,$whichline,
-                    \@alphabet,\%record,$scantron_config,$scan_data);
+                    \@alphabet,\%record,$scantron_config,$scan_data,
+                    $randomorder,$randompick,$respnumlookup);
             } else {
                 $ansnum = &scantron_validator_positional($ansnum,$questnum,
                     $quest_id,$answers_needed,$currentquest,$whichline,
-                    \@alphabet,\%record,$scantron_config,$scan_data);
+                    \@alphabet,\%record,$scantron_config,$scan_data,
+                    $randomorder,$randompick,$respnumlookup);
             }
         }
     }
@@ -5638,9 +6466,66 @@ sub scantron_parse_scanline {
     return \%record;
 }
 
+sub get_master_seq {
+    my ($resources,$master_seq,$symb_to_resource) = @_;
+    return unless ((ref($resources) eq 'ARRAY') && (ref($master_seq) eq 'ARRAY') && 
+                   (ref($symb_to_resource) eq 'HASH'));
+    my $resource_error;
+    foreach my $resource (@{$resources}) {
+        my $ressymb;
+        if (ref($resource)) {
+            $ressymb = $resource->symb();
+            push(@{$master_seq},$ressymb);
+            $symb_to_resource->{$ressymb} = $resource;
+        } else {
+            $resource_error = 1;
+            last;
+        }
+    }
+    return $resource_error;
+}
+
+sub get_respnum_lookups {
+    my ($sequence,$scan_data,$idmap,$line,$record,$master_seq,$symb_to_resource,
+        $partids_by_symb,$orderedforcode,$respnumlookup,$startline) = @_;
+    return unless ((ref($record) eq 'HASH') && (ref($master_seq) eq 'ARRAY') &&
+                   (ref($symb_to_resource) eq 'HASH') && (ref($partids_by_symb) eq 'HASH') &&
+                   (ref($orderedforcode) eq 'HASH') && (ref($respnumlookup) eq 'HASH') &&
+                   (ref($startline) eq 'HASH'));
+    my ($user,$scancode);
+    if ((exists($record->{'scantron.CODE'})) &&
+        (&Apache::lonnet::validCODE($record->{'scantron.CODE'}))) {
+        $scancode = $record->{'scantron.CODE'};
+    } else {
+        $user = &scantron_find_student($record,$scan_data,$idmap,$line);
+    }
+    my @mapresources =
+        &users_order($user,$scancode,$sequence,$master_seq,$symb_to_resource,
+                     $orderedforcode);
+    my $total = 0;
+    my $count = 0;
+    foreach my $resource (@mapresources) {
+        my $id = $resource->id();
+        my $symb = $resource->symb();
+        if (ref($partids_by_symb->{$symb}) eq 'ARRAY') {
+            foreach my $partid (@{$partids_by_symb->{$symb}}) {
+                my $respnum = $masterseq_id_responsenum{$id.'_'.$partid};
+                if ($respnum ne '') {
+                    $respnumlookup->{$count} = $respnum;
+                    $startline->{$count} = $total;
+                    $total += $bubble_lines_per_response{$respnum};
+                    $count ++;
+                }
+            }
+        }
+    }
+    return $total;
+}
+
 sub scantron_validator_lettnum {
     my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
-        $alphabet,$record,$scantron_config,$scan_data) = @_;
+        $alphabet,$record,$scantron_config,$scan_data,$randomorder,
+        $randompick,$respnumlookup) = @_;
 
     # Qon 'letter' implies for each slot in currquest we have:
     #    ? or * for doubles, a letter in A-Z for a bubble, and
@@ -5659,19 +6544,23 @@ sub scantron_validator_lettnum {
         $matchon = '\d';
     }
     my $occurrences = 0;
-    if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
-        ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
-        ($responsetype_per_response{$questnum-1} eq 'stringresponse') ||
-        ($responsetype_per_response{$questnum-1} eq 'imageresponse') ||
-        ($responsetype_per_response{$questnum-1} eq 'reactionresponse') ||
-        ($responsetype_per_response{$questnum-1} eq 'organicresponse')) {
+    my $responsenum = $questnum-1;
+    if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
+       $responsenum = $respnumlookup->{$questnum-1} 
+    }
+    if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
+        ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
+        ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
+        ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
+        ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
+        ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
         my @singlelines = split('',$currquest);
         foreach my $entry (@singlelines) {
             $occurrences = &occurence_count($entry,$matchon);
             if ($occurrences > 1) {
                 last;
             }
-        } 
+        }
     } else {
         $occurrences = &occurence_count($currquest,$matchon); 
     }
@@ -5719,7 +6608,8 @@ sub scantron_validator_lettnum {
 
 sub scantron_validator_positional {
     my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
-        $whichline,$alphabet,$record,$scantron_config,$scan_data) = @_;
+        $whichline,$alphabet,$record,$scantron_config,$scan_data,
+        $randomorder,$randompick,$respnumlookup) = @_;
 
     # Otherwise there's a positional notation;
     # each bubble line requires Qlength items, and there are filled in
@@ -5761,12 +6651,16 @@ sub scantron_validator_positional {
         #  record all the bubbles filled in as well as the
         #  fact this response consists of multiple bubbles.
         #
-        if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
-            ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
-            ($responsetype_per_response{$questnum-1} eq 'stringresponse') ||
-            ($responsetype_per_response{$questnum-1} eq 'imageresponse') ||
-            ($responsetype_per_response{$questnum-1} eq 'reactionresponse') ||
-            ($responsetype_per_response{$questnum-1} eq 'organicresponse')) {
+        my $responsenum = $questnum-1;
+        if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
+            $responsenum = $respnumlookup->{$questnum-1}
+        }
+        if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
+            ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
+            ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
+            ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
+            ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
+            ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
             my $doubleerror = 0;
             while (($currquest >= $$scantron_config{'Qlength'}) && 
                    (!$doubleerror)) {
@@ -5899,7 +6793,7 @@ sub scantron_filter {
 
 sub scantron_process_corrections {
     my ($r) = @_;
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
     my $classlist=&Apache::loncoursedata::get_classlist();
     my $which=$env{'form.scantron_line'};
@@ -5948,7 +6842,11 @@ sub scantron_process_corrections {
 	}
     }
     if ($err) {
-	$r->print("<span class=\"LC_warning\">Unable to accept last correction, an error occurred :$errmsg:</span>");
+        $r->print(
+            '<p class="LC_error">'
+           .&mt('Unable to accept last correction, an error occurred: [_1]',
+                $errmsg)
+           .'</p>');
     } else {
 	&scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
 	&scantron_putfile($scanlines,$scan_data);
@@ -6040,7 +6938,7 @@ sub remember_current_skipped {
 =item check_for_error
 
     Checks if there was an error when attempting to remove a specific
-    scantron_.. bubble sheet data file. Prints out an error if
+    scantron_.. bubblesheet data file. Prints out an error if
     something went wrong.
 
 =cut
@@ -6062,32 +6960,37 @@ sub check_for_error {
 =cut
 
 sub scantron_warning_screen {
-    my ($button_text)=@_;
+    my ($button_text,$symb)=@_;
     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my $CODElist;
     if ($scantron_config{'CODElocation'} &&
 	$scantron_config{'CODEstart'} &&
 	$scantron_config{'CODElength'}) {
 	$CODElist=$env{'form.scantron_CODElist'};
-	if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">None</span>'; }
+	if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">'.&mt('None').'</span>'; }
 	$CODElist=
 	    '<tr><td><b>'.&mt('List of CODES to validate against:').'</b></td><td><tt>'.
 	    $env{'form.scantron_CODElist'}.'</tt></td></tr>';
     }
+    my $lastbubblepoints;
+    if ($env{'form.scantron_lastbubblepoints'} ne '') {
+        $lastbubblepoints =
+            '<tr><td><b>'.&mt('Hand-graded items: points from last bubble in row').'</b></td><td><tt>'.
+            $env{'form.scantron_lastbubblepoints'}.'</tt></td></tr>';
+    }
     return ('
 <p>
 <span class="LC_warning">
-'.&mt('Please double check the information below before clicking on \'[_1]\'',&mt($button_text)).'</span>
+'.&mt("Please double check the information below before clicking on '[_1]'",&mt($button_text)).'</span>
 </p>
 <table>
 <tr><td><b>'.&mt('Sequence to be Graded:').'</b></td><td>'.$title.'</td></tr>
 <tr><td><b>'.&mt('Data File that will be used:').'</b></td><td><tt>'.$env{'form.scantron_selectfile'}.'</tt></td></tr>
-'.$CODElist.'
+'.$CODElist.$lastbubblepoints.'
 </table>
-<br />
-<p> '.&mt('If this information is correct, please click on \'[_1]\'.',&mt($button_text)).'</p>
-<p> '.&mt('If something is incorrect, please click the \'Grading Menu\' button to start over.').'</p>
+<p> '.&mt("If this information is correct, please click on '[_1]'.",&mt($button_text)).'<br />
+'.&mt('If something is incorrect, please return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','<a href="/adm/grades?symb='.$symb.'&command=scantron_selectphase" class="LC_info">','</a>').'</p>
 
 <br />
 ');
@@ -6103,33 +7006,33 @@ sub scantron_warning_screen {
 =cut
 
 sub scantron_do_warning {
-    my ($r)=@_;
-    my ($symb)=&get_symb($r);
+    my ($r,$symb)=@_;
     if (!$symb) {return '';}
     my $default_form_data=&defaultFormData($symb);
     $r->print(&scantron_form_start().$default_form_data);
     if ( $env{'form.selectpage'} eq '' ||
 	 $env{'form.scantron_selectfile'} eq '' ||
 	 $env{'form.scantron_format'} eq '' ) {
-	$r->print("<p>".&mt('You have forgetten to specify some information. Please go Back and try again.')."</p>");
+	$r->print("<p>".&mt('You have forgotten to specify some information. Please go Back and try again.')."</p>");
 	if ( $env{'form.selectpage'} eq '') {
 	    $r->print('<p><span class="LC_error">'.&mt('You have not selected a Sequence to grade').'</span></p>');
 	} 
 	if ( $env{'form.scantron_selectfile'} eq '') {
-	    $r->print('<p><span class="LC_error">'.&mt('You have not selected a file that contains the student\'s response data.').'</span></p>');
+	    $r->print('<p><span class="LC_error">'.&mt("You have not selected a file that contains the student's response data.").'</span></p>');
 	} 
 	if ( $env{'form.scantron_format'} eq '') {
-	    $r->print('<p><span class="LC_error">'.&mt('You have not selected a the format of the student\'s response data.').'</span></p>');
+	    $r->print('<p><span class="LC_error">'.&mt("You have not selected the format of the student's response data.").'</span></p>');
 	} 
     } else {
-	my $warning=&scantron_warning_screen('Grading: Validate Records');
+	my $warning=&scantron_warning_screen('Grading: Validate Records',$symb);
+        my $bubbledbyhand=&hand_bubble_option();
 	$r->print('
-'.$warning.'
+'.$warning.$bubbledbyhand.'
 <input type="submit" name="submit" value="'.&mt('Grading: Validate Records').'" />
 <input type="hidden" name="command" value="scantron_validate" />
 ');
     }
-    $r->print("</form><br />".&show_grading_menu_form($symb));
+    $r->print("</form><br />");
     return '';
 }
 
@@ -6166,9 +7069,11 @@ SCANTRONFORM
            '<input type="hidden" name="scantron.sub_bubblelines.'.$line.'" value="'.$env{"form.scantron.sub_bubblelines.$line"}.'" />'."\n";
        $chunk .=
            '<input type="hidden" name="scantron.responsetype.'.$line.'" value="'.$env{"form.scantron.responsetype.$line"}.'" />'."\n";
+       $chunk .=
+           '<input type="hidden" name="scantron.residpart.'.$line.'" value="'.$env{"form.scantron.residpart.$line"}.'" />'."\n";
        $result .= $chunk;
        $line++;
-   }
+    }
     return $result;
 }
 
@@ -6176,7 +7081,7 @@ SCANTRONFORM
 
 =item scantron_validate_file
 
-    Dispatch routine for doing validation of a bubble sheet data file.
+    Dispatch routine for doing validation of a bubblesheet data file.
 
     Also processes any necessary information resets that need to
     occur before validation begins (ignore previous corrections,
@@ -6185,12 +7090,11 @@ SCANTRONFORM
 =cut
 
 sub scantron_validate_file {
-    my ($r) = @_;
-    my ($symb)=&get_symb($r);
+    my ($r,$symb) = @_;
     if (!$symb) {return '';}
     my $default_form_data=&defaultFormData($symb);
     
-    # do the detection of only doing skipped records first befroe we delete
+    # do the detection of only doing skipped records first before we delete
     # them when doing the corrections reset
     if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
 	&reset_skipping_status();
@@ -6213,8 +7117,17 @@ sub scantron_validate_file {
     $r->print('<p>'.&mt('Gathering necessary information.').'</p>');$r->rflush();
     #get the student pick code ready
     $r->print(&Apache::loncommon::studentbrowser_javascript());
-    my $max_bubble=&scantron_get_maxbubble();
+    my $nav_error;
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
+    my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
+    if ($nav_error) {
+        $r->print(&navmap_errormsg());
+        return '';
+    }
     my $result=&scantron_form_start($max_bubble).$default_form_data;
+    if ($env{'form.scantron_lastbubblepoints'} ne '') {
+        $result .= '<input type="hidden" name="scantron_lastbubblepoints" value="'.$env{'form.scantron_lastbubblepoints'}.'" />';
+    }
     $r->print($result);
     
     my @validate_phases=( 'sequence',
@@ -6232,6 +7145,7 @@ sub scantron_validate_file {
     while (!$stop && $currentphase < scalar(@validate_phases)) {
 	$r->print(&mt('Validating '.$validate_phases[$currentphase]).'<br />');
 	$r->rflush();
+     
 	my $which="scantron_validate_".$validate_phases[$currentphase];
 	{
 	    no strict 'refs';
@@ -6239,7 +7153,7 @@ sub scantron_validate_file {
 	}
     }
     if (!$stop) {
-	my $warning=&scantron_warning_screen('Start Grading');
+	my $warning=&scantron_warning_screen('Start Grading',$symb);
 	$r->print(&mt('Validation process complete.').'<br />'.
                   $warning.
                   &mt('Perform verification for each student after storage of submissions?').
@@ -6249,7 +7163,7 @@ sub scantron_validate_file {
                   '<input type="radio" name="verifyrecord" value="0" checked="checked" />'.&mt('No').
                   '</label></span><br />'.
                   &mt('Grading will take longer if you use verification.').'<br />'.
-                  &mt("Alternatively, the 'Review bubblesheet data' utility (see grading menu) can be used for all students after grading is complete.").'<br /><br />'.
+                  &mt('Otherwise, Grade/Manage/Review Bubblesheets [_1] Review bubblesheet data can be used once grading is complete.','&raquo;').'<br /><br />'.
                   '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'.
                   '<input type="hidden" name="command" value="scantron_process" />'."\n");
     } else {
@@ -6261,7 +7175,7 @@ sub scantron_validate_file {
 	    $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' &rarr; " />');
 	    $r->print(' '.&mt('this error').' <br />');
 
-	    $r->print(" <p>".&mt("Or click the 'Grading Menu' button to start over.")."</p>");
+	    $r->print('<p>'.&mt('Or return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','<a href="/adm/grades?symb='.$symb.'&command=scantron_selectphase" class="LC_info">','</a>').'</p>');
 	} else {
             if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
 	        $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' &rarr;" onclick="javascript:verify_bubble_radio(this.form)" />');
@@ -6273,7 +7187,7 @@ sub scantron_validate_file {
 	    $r->print(" ".&mt("this scanline saving it for later."));
 	}
     }
-    $r->print(" </form><br />".&show_grading_menu_form($symb));
+    $r->print(" </form><br />");
     return '';
 }
 
@@ -6282,7 +7196,7 @@ sub scantron_validate_file {
 
 =item scantron_remove_file
 
-   Removes the requested bubble sheet data file, makes sure that
+   Removes the requested bubblesheet data file, makes sure that
    scantron_original_<filename> is never removed
 
 
@@ -6307,7 +7221,7 @@ sub scantron_remove_file {
 
 =item scantron_remove_scan_data
 
-   Removes all scan_data correction for the requested bubble sheet
+   Removes all scan_data correction for the requested bubblesheet
    data file.  (In the case that both the are doing skipped records we need
    to remember the old skipped lines for the time being so that element
    persists for a while.)
@@ -6344,7 +7258,7 @@ sub scantron_remove_scan_data {
 
 =item scantron_getfile
 
-    Fetches the requested bubble sheet data file (all 3 versions), and
+    Fetches the requested bubblesheet data file (all 3 versions), and
     the scan_data hash
   
   Arguments:
@@ -6444,7 +7358,7 @@ sub lonnet_putfile {
 
 =item scantron_putfile
 
-    Stores the current version of the bubble sheet data files, and the
+    Stores the current version of the bubblesheet data files, and the
     scan_data hash. (Does not modify the original version only the
     corrected and skipped versions.
 
@@ -6538,7 +7452,7 @@ sub get_todo_count {
 
 =item scantron_put_line
 
-    Updates the 'corrected' or 'skipped' versions of the bubble sheet
+    Updates the 'corrected' or 'skipped' versions of the bubblesheet
     data file.
 
  Arguments:
@@ -6625,6 +7539,10 @@ sub scantron_validate_sequence {
     my ($r,$currentphase) = @_;
 
     my $navmap=Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        $r->print(&navmap_errormsg());
+        return (1,$currentphase);
+    }
     my (undef,undef,$sequence)=
 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
 
@@ -6636,7 +7554,13 @@ sub scantron_validate_sequence {
 	my @resources=
 	    $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
 	if (@resources) {
-	    $r->print("<p>".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."</p>");
+	    $r->print(
+                '<p class="LC_warning">'
+               .&mt('Some resources in the sequence currently are not set to'
+                   .' bubblesheet exam mode. Grading these resources currently may not'
+                   .' work correctly.')
+               .'</p>'
+            );
 	    return (1,$currentphase);
 	}
     }
@@ -6654,10 +7578,15 @@ sub scantron_validate_ID {
     my %idmap=&username_to_idmap($classlist);
 
     #get scantron line setup
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
-    
-    &scantron_get_maxbubble();	# parse needs the bubble_lines.. array.
+
+    my $nav_error;
+    &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble_lines.. array.
+    if ($nav_error) {
+        $r->print(&navmap_errormsg());
+        return(1,$currentphase);
+    }
 
     my %found=('ids'=>{},'usernames'=>{});
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
@@ -6712,25 +7641,35 @@ sub scantron_validate_ID {
 
 
 sub scantron_get_correction {
-    my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
+    my ($r,$i,$scan_record,$scan_config,$line,$error,$arg,
+        $randomorder,$randompick,$respnumlookup,$startline)=@_;
 #FIXME in the case of a duplicated ID the previous line, probably need
 #to show both the current line and the previous one and allow skipping
 #the previous one or the current one
 
     if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
-	$r->print("<p>".&mt("<b>An error was detected ($error)</b>".
-			    " for PaperID <tt>[_1]</tt>",
-			    $$scan_record{'scantron.PaperID'})."</p> \n");
-    } else {
-	$r->print("<p>".&mt("<b>An error was detected ($error)</b>".
-			    " in scanline [_1] <pre>[_2]</pre>",
-			    $i,$line)."</p> \n");
-    }
-    my $message="<p>".&mt("The ID on the form is  <tt>[_1]</tt><br />".
-			  "The name on the paper is [_2],[_3]",
-			  $$scan_record{'scantron.ID'},
-			  $$scan_record{'scantron.LastName'},
-			  $$scan_record{'scantron.FirstName'})."</p>";
+        $r->print(
+            '<p class="LC_warning">'
+           .&mt('An error was detected ([_1]) for PaperID [_2]',
+                "<b>$error</b>",
+                '<tt>'.$$scan_record{'scantron.PaperID'}.'</tt>')
+           ."</p> \n");
+    } else {
+        $r->print(
+            '<p class="LC_warning">'
+           .&mt('An error was detected ([_1]) in scanline [_2] [_3]',
+                "<b>$error</b>", $i, "<pre>$line</pre>")
+           ."</p> \n");
+    }
+    my $message =
+        '<p>'
+       .&mt('The ID on the form is [_1]',
+            "<tt>$$scan_record{'scantron.ID'}</tt>")
+       .'<br />'
+       .&mt('The name on the paper is [_1], [_2]',
+            $$scan_record{'scantron.LastName'},
+            $$scan_record{'scantron.FirstName'})
+       .'</p>';
 
     $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
@@ -6740,10 +7679,10 @@ sub scantron_get_correction {
 
     if ($error =~ /ID$/) {
 	if ($error eq 'incorrectID') {
-	    $r->print("<p>".&mt("The encoded ID is not in the classlist").
+            $r->print('<p class="LC_warning">'.&mt("The encoded ID is not in the classlist").
 		      "</p>\n");
 	} elsif ($error eq 'duplicateID') {
-	    $r->print("<p>".&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."</p>\n");
+            $r->print('<p class="LC_warning">'.&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."</p>\n");
 	}
 	$r->print($message);
 	$r->print("<p>".&mt("How should I handle this?")." <br /> \n");
@@ -6753,20 +7692,21 @@ sub scantron_get_correction {
 	$r->print(&Apache::loncommon::selectstudent_link('scantronupload',
 				       'scantron_username','scantron_domain'));
 	$r->print(": <input type='text' name='scantron_username' value='' />");
-	$r->print("\n@".
+	$r->print("\n:\n".
 		 &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
 
 	$r->print('</li>');
     } elsif ($error =~ /CODE$/) {
 	if ($error eq 'incorrectCODE') {
-	    $r->print("<p>".&mt("The encoded CODE is not in the list of possible CODEs.")."</p>\n");
+	    $r->print('<p class="LC_warning">'.&mt("The encoded CODE is not in the list of possible CODEs.")."</p>\n");
 	} elsif ($error eq 'duplicateCODE') {
-	    $r->print("<p>".&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."</p>\n");
+	    $r->print('<p class="LC_warning">'.&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."</p>\n");
 	}
-	$r->print("<p>".&mt("The CODE on the form is  <tt>'[_1]'</tt>",
-			    $$scan_record{'scantron.CODE'})."<br />\n");
+	$r->print("<p>".&mt('The CODE on the form is [_1]',
+			    "<tt>'$$scan_record{'scantron.CODE'}'</tt>")
+                 ."</p>\n");
 	$r->print($message);
-	$r->print("<p>".&mt("How should I handle this?")." <br /> \n");
+	$r->print("<p>".&mt("How should I handle this?")."</p>\n");
 	$r->print("\n<br /> ");
 	my $i=0;
 	if ($error eq 'incorrectCODE' 
@@ -6793,14 +7733,13 @@ sub scantron_get_correction {
 	    $r->print("
     <label>
         <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked />
-       ".&mt("Use the CODE [_1] that is was on the paper, ignoring the error.",
+       ".&mt("Use the CODE [_1] that was on the paper, ignoring the error.",
 	     "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
     </label>");
 	    $r->print("\n<br />");
 	}
 
-	$r->print(<<ENDSCRIPT);
-<script type="text/javascript">
+	$r->print(&Apache::lonhtmlcommon::scripttag(<<ENDSCRIPT));
 function change_radio(field) {
     var slct=document.scantronupload.scantron_CODE_resolution;
     var i;
@@ -6808,7 +7747,6 @@ function change_radio(field) {
         if (slct[i].value==field) { slct[i].checked=true; }
     }
 }
-</script>
 ENDSCRIPT
 	my $href="/adm/pickcode?".
 	   "form=".&escape("scantronupload").
@@ -6833,12 +7771,13 @@ ENDSCRIPT
 	     "</label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" />"));
 	$r->print("\n<br /><br />");
     } elsif ($error eq 'doublebubble') {
-	$r->print("<p>".&mt("There have been multiple bubbles scanned for some question(s)")."</p>\n");
+	$r->print('<p class="LC_warning">'.&mt("There have been multiple bubbles scanned for some question(s)")."</p>\n");
 
 	# The form field scantron_questions is acutally a list of line numbers.
 	# represented by this form so:
 
-	my $line_list = &questions_to_line_list($arg);
+	my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
+                                                $respnumlookup,$startline);
 
 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 		  $line_list.'" />');
@@ -6846,12 +7785,14 @@ ENDSCRIPT
 	$r->print("<p>".&mt("Please indicate which bubble should be used for grading")."</p>");
 	foreach my $question (@{$arg}) {
 	    my @linenums = &prompt_for_corrections($r,$question,$scan_config,
-                                                   $scan_record, $error);
+                                                   $scan_record, $error,
+                                                   $randomorder,$randompick,
+                                                   $respnumlookup,$startline);
             push(@lines_to_correct,@linenums);
 	}
         $r->print(&verify_bubbles_checked(@lines_to_correct));
     } elsif ($error eq 'missingbubble') {
-	$r->print("<p>".&mt("There have been <b>no</b> bubbles scanned for some question(s)")."</p>\n");
+	$r->print('<p class="LC_warning">'.&mt("There have been [_1]no[_2] bubbles scanned for some question(s)",'<b>','</b>')."</p>\n");
 	$r->print($message);
 	$r->print("<p>".&mt("Please indicate which bubble should be used for grading.")."</p>");
 	$r->print(&mt("Some questions have no scanned bubbles.")."\n");
@@ -6859,14 +7800,17 @@ ENDSCRIPT
 	# The form field scantron_questions is actually a list of line numbers not
 	# a list of question numbers. Therefore:
 	#
-	
-	my $line_list = &questions_to_line_list($arg);
+
+	my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
+                                                $respnumlookup,$startline);
 
 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 		  $line_list.'" />');
 	foreach my $question (@{$arg}) {
 	    my @linenums = &prompt_for_corrections($r,$question,$scan_config,
-                                                   $scan_record, $error);
+                                                   $scan_record, $error,
+                                                   $randomorder,$randompick,
+                                                   $respnumlookup,$startline);
             push(@lines_to_correct,@linenums);
 	}
         $r->print(&verify_bubbles_checked(@lines_to_correct));
@@ -6880,8 +7824,8 @@ sub verify_bubbles_checked {
     my (@ansnums) = @_;
     my $ansnumstr = join('","',@ansnums);
     my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
-    my $output = (<<ENDSCRIPT);
-<script type="text/javascript">
+    &js_escape(\$warning);
+    my $output = &Apache::lonhtmlcommon::scripttag((<<ENDSCRIPT));
 function verify_bubble_radio(form) {
     var ansnumArray = new Array ("$ansnumstr");
     var need_bubble_count = 0;
@@ -6904,7 +7848,6 @@ function verify_bubble_radio(form) {
     }
     form.submit(); 
 }
-</script>
 ENDSCRIPT
     return $output;
 }
@@ -6919,12 +7862,20 @@ used to fill in the scantron_questions f
 
   Arguments:
      questions    - Reference to an array of questions.
+     randomorder  - True if randomorder in use.
+     randompick   - True if randompick in use.
+     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).
+     startline    - Reference to hash where key is question number (0 is first)
+                    and key is number of first bubble line for current student
+                    or code-based randompick and/or randomorder.
 
 =cut
 
 
 sub questions_to_line_list {
-    my ($questions) = @_;
+    my ($questions,$randomorder,$randompick,$respnumlookup,$startline) = @_;
     my @lines;
 
     foreach my $item (@{$questions}) {
@@ -6933,8 +7884,16 @@ sub questions_to_line_list {
         if ($item =~ /^(\d+)\.(\d+)$/) {
             $question = $1;
             my $subquestion = $2;
-            $first = $first_bubble_line{$question-1} + 1;
-            my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+            my $responsenum = $question-1;
+            if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
+                $responsenum = $respnumlookup->{$question-1};
+                if (ref($startline) eq 'HASH') {
+                    $first = $startline->{$question-1} + 1;
+                }
+            } else {
+                $first = $first_bubble_line{$responsenum} + 1;
+            }
+            my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
             my $subcount = 1;
             while ($subcount<$subquestion) {
                 $first += $subans[$subcount-1];
@@ -6942,8 +7901,16 @@ sub questions_to_line_list {
             }
             $count = $subans[$subquestion-1];
         } else {
-	    $first   = $first_bubble_line{$question-1} + 1;
-	    $count   = $bubble_lines_per_response{$question-1};
+            my $responsenum = $question-1;
+            if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
+                $responsenum = $respnumlookup->{$question-1};
+                if (ref($startline) eq 'HASH') {
+                    $first = $startline->{$question-1} + 1;
+                }
+            } else {
+                $first = $first_bubble_line{$responsenum} + 1;
+            }
+	    $count   = $bubble_lines_per_response{$responsenum};
         }
         $last = $first+$count-1;
         push(@lines, ($first..$last));
@@ -6965,6 +7932,15 @@ for multi and missing bubble cases).
    $scan_config - The scantron file configuration hash.
    $scan_record - Reference to the hash that has the the parsed scanlines.
    $error       - Type of error
+   $randomorder - True if randomorder in use.
+   $randompick  - True if randompick in use.
+   $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).
+   $startline   - Reference to hash where key is question number (0 is first)
+                  and value is number of first bubble line for current student
+                  or code-based randompick and/or randomorder.
+
 
  Implicit inputs:
    %bubble_lines_per_response   - Starting line numbers for each question.
@@ -6989,15 +7965,26 @@ for multi and missing bubble cases).
 =cut
 
 sub prompt_for_corrections {
-    my ($r, $question, $scan_config, $scan_record, $error) = @_;
+    my ($r, $question, $scan_config, $scan_record, $error, $randomorder,
+        $randompick, $respnumlookup, $startline) = @_;
     my ($current_line,$lines);
     my @linenums;
     my $questionnum = $question;
+    my ($first,$responsenum);
     if ($question =~ /^(\d+)\.(\d+)$/) {
         $question = $1;
-        $current_line = $first_bubble_line{$question-1} + 1 ;
         my $subquestion = $2;
-        my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+        if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
+            $responsenum = $respnumlookup->{$question-1};
+            if (ref($startline) eq 'HASH') {
+                $first = $startline->{$question-1};
+            }
+        } else {
+            $responsenum = $question-1;
+            $first = $first_bubble_line{$responsenum};
+        }
+        $current_line = $first + 1 ;
+        my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
         my $subcount = 1;
         while ($subcount<$subquestion) {
             $current_line += $subans[$subcount-1];
@@ -7005,25 +7992,43 @@ sub prompt_for_corrections {
         }
         $lines = $subans[$subquestion-1];
     } else {
-        $current_line = $first_bubble_line{$question-1} + 1 ;
-        $lines        = $bubble_lines_per_response{$question-1};
+        if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
+            $responsenum = $respnumlookup->{$question-1};
+            if (ref($startline) eq 'HASH') { 
+                $first = $startline->{$question-1};
+            }
+        } else {
+            $responsenum = $question-1;
+            $first = $first_bubble_line{$responsenum};
+        }
+        $current_line = $first + 1;
+        $lines        = $bubble_lines_per_response{$responsenum};
     }
     if ($lines > 1) {
         $r->print(&mt('The group of bubble lines below responds to a single question.').'<br />');
-        if (($responsetype_per_response{$question-1} eq 'essayresponse') ||
-            ($responsetype_per_response{$question-1} eq 'formularesponse') ||
-            ($responsetype_per_response{$question-1} eq 'stringresponse') ||
-            ($responsetype_per_response{$question-1} eq 'imageresponse') ||
-            ($responsetype_per_response{$question-1} eq 'reactionresponse') ||
-            ($responsetype_per_response{$question-1} eq 'organicresponse')) {
-            $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the exam directed students to leave [quant,_1,line] blank on their bubblesheets.",$lines).'<br /><br />'.&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.').'<br />'.&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.').'<br />'.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'<br /><br />');
+        if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
+            ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
+            ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
+            ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
+            ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
+            ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
+            $r->print(
+                &mt("Although this particular question type requires handgrading, the instructions for this question in the bubblesheet exam directed students to leave [quant,_1,line] blank on their bubblesheets.",$lines)
+               .'<br /><br />'
+               .&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.')
+               .'<br />'
+               .&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.')
+               .'<br />'
+               .&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.")
+               .'<br /><br />'
+            );
         } else {
             $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");
         }
     }
     for (my $i =0; $i < $lines; $i++) {
         my $selected = $$scan_record{"scantron.$current_line.answer"};
-	&scantron_bubble_selector($r,$scan_config,$current_line, 
+	&scantron_bubble_selector($r,$scan_config,$current_line,
 	        		  $questionnum,$error,split('', $selected));
         push(@linenums,$current_line);
 	$current_line++;
@@ -7043,7 +8048,7 @@ sub prompt_for_corrections {
 
  Arguments:
     $r           - Apache request object
-    $scan_config - hash from &get_scantron_config()
+    $scan_config - hash from &Apache::lonnet::get_scantron_config()
     $line        - Number of the line being displayed.
     $questionnum - Question number (may include subquestion)
     $error       - Type of error.
@@ -7056,7 +8061,19 @@ sub scantron_bubble_selector {
     my $max=$$scan_config{'Qlength'};
 
     my $scmode=$$scan_config{'Qon'};
-    if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }	     
+    if ($scmode eq 'number' || $scmode eq 'letter') { 
+        if (($$scan_config{'BubblesPerRow'} =~ /^\d+$/) &&
+            ($$scan_config{'BubblesPerRow'} > 0)) {
+            $max=$$scan_config{'BubblesPerRow'};
+            if (($scmode eq 'number') && ($max > 10)) {
+                $max = 10;
+            } elsif (($scmode eq 'letter') && $max > 26) {
+                $max = 26;
+            }
+        } else {
+            $max = 10;
+        }
+    }
 
     my @alphabet=('A'..'Z');
     $r->print(&Apache::loncommon::start_data_table().
@@ -7195,7 +8212,7 @@ sub get_codes {
 
 sub scantron_validate_CODE {
     my ($r,$currentphase) = @_;
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     if ($scantron_config{'CODElocation'} &&
 	$scantron_config{'CODEstart'} &&
 	$scantron_config{'CODElength'}) {
@@ -7210,7 +8227,12 @@ sub scantron_validate_CODE {
 
     my %allcodes=&get_codes();
 
-    &scantron_get_maxbubble();	# parse needs the lines per response array.
+    my $nav_error;
+    &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the lines per response array.
+    if ($nav_error) {
+        $r->print(&navmap_errormsg());
+        return(1,$currentphase);
+    }
 
     my ($scanlines,$scan_data)=&scantron_getfile();
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
@@ -7260,21 +8282,61 @@ sub scantron_validate_doublebubble {
     #get student info
     my $classlist=&Apache::loncoursedata::get_classlist();
     my %idmap=&username_to_idmap($classlist);
+    my (undef,undef,$sequence)=
+        &Apache::lonnet::decode_symb($env{'form.selectpage'});
 
     #get scantron line setup
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
-    &scantron_get_maxbubble();	# parse needs the bubble line array.
+
+    my $navmap = Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        $r->print(&navmap_errormsg());
+        return(1,$currentphase);
+    }
+    my $map=$navmap->getResourceByUrl($sequence);
+    my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+    my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
+        %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
+    my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
+
+    my $nav_error;
+    if (ref($map)) {
+        $randomorder = $map->randomorder();
+        $randompick = $map->randompick();
+        if ($randomorder || $randompick) {
+            $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+            if ($nav_error) {
+                $r->print(&navmap_errormsg());
+                return(1,$currentphase);
+            }
+            &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+                                    \%grader_randomlists_by_symb,$bubbles_per_row);
+        }
+    } else {
+        $r->print(&navmap_errormsg());
+        return(1,$currentphase);
+    }
+
+    &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble line array.
+    if ($nav_error) {
+        $r->print(&navmap_errormsg());
+        return(1,$currentphase);
+    }
 
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 	if ($line=~/^[\s\cz]*$/) { next; }
 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
-						 $scan_data);
+						 $scan_data,undef,\%idmap,$randomorder,
+                                                 $randompick,$sequence,\@master_seq,
+                                                 \%symb_to_resource,\%grader_partids_by_symb,
+                                                 \%orderedforcode,\%respnumlookup,\%startline);
 	if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
 	&scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
 				 'doublebubble',
-				 $$scan_record{'scantron.doubleerror'});
+				 $$scan_record{'scantron.doubleerror'},
+                                 $randomorder,$randompick,\%respnumlookup,\%startline);
     	return (1,$currentphase);
     }
     return (0,$currentphase+1);
@@ -7282,6 +8344,7 @@ sub scantron_validate_doublebubble {
 
 
 sub scantron_get_maxbubble {
+    my ($nav_error,$scantron_config) = @_;
     if (defined($env{'form.scantron_maxbubble'}) &&
 	$env{'form.scantron_maxbubble'}) {
 	&restore_bubble_lines();
@@ -7292,8 +8355,15 @@ sub scantron_get_maxbubble {
 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
 
     my $navmap=Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        if (ref($nav_error)) {
+            $$nav_error = 1;
+        }
+        return;
+    }
     my $map=$navmap->getResourceByUrl($sequence);
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+    my $bubbles_per_row = &bubblesheet_bubbles_per_row($scantron_config);
 
     &Apache::lonxml::clear_problem_counter();
 
@@ -7305,11 +8375,14 @@ sub scantron_get_maxbubble {
     %first_bubble_line         = ();
     %subdivided_bubble_lines   = ();
     %responsetype_per_response = ();
+    %masterseq_id_responsenum  = ();
 
     my $response_number = 0;
     my $bubble_line     = 0;
     foreach my $resource (@resources) {
-        my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom);
+        my $resid = $resource->id(); 
+        my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,
+                                                          $udom,undef,$bubbles_per_row);
         if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
 	    foreach my $part_id (@{$parts}) {
                 my $lines;
@@ -7338,9 +8411,10 @@ sub scantron_get_maxbubble {
                     if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
                         $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
                     }
-                    my $bubbles_per_line = 10;
-                    my $inner_bubble_lines = int($numbub/$bubbles_per_line);
-                    if (($numbub % $bubbles_per_line) != 0) {
+                    my $bubbles_per_row =
+                        &bubblesheet_bubbles_per_row($scantron_config);
+                    my $inner_bubble_lines = int($numbub/$bubbles_per_row);
+                    if (($numbub % $bubbles_per_row) != 0) {
                         $inner_bubble_lines++;
                     }
                     for (my $i=0; $i<$numshown; $i++) {
@@ -7351,12 +8425,13 @@ sub scantron_get_maxbubble {
                     $lines = $numshown * $inner_bubble_lines;
                 } else {
                     $lines = $analysis->{"$part_id.bubble_lines"};
-                } 
+                }
 
                 $first_bubble_line{$response_number} = $bubble_line;
 	        $bubble_lines_per_response{$response_number} = $lines;
                 $responsetype_per_response{$response_number} = 
                     $analysis->{$part_id.'.type'};
+                $masterseq_id_responsenum{$resid.'_'.$part_id} = $response_number;  
 	        $response_number++;
 
 	        $bubble_line +=  $lines;
@@ -7372,22 +8447,76 @@ sub scantron_get_maxbubble {
     return $env{'form.scantron_maxbubble'};
 }
 
+sub bubblesheet_bubbles_per_row {
+    my ($scantron_config) = @_;
+    my $bubbles_per_row;
+    if (ref($scantron_config) eq 'HASH') {
+        $bubbles_per_row = $scantron_config->{'BubblesPerRow'};
+    }
+    if ((!$bubbles_per_row) || ($bubbles_per_row < 1)) {
+        $bubbles_per_row = 10;
+    }
+    return $bubbles_per_row;
+}
+
 sub scantron_validate_missingbubbles {
     my ($r,$currentphase) = @_;
     #get student info
     my $classlist=&Apache::loncoursedata::get_classlist();
     my %idmap=&username_to_idmap($classlist);
+    my (undef,undef,$sequence)=
+        &Apache::lonnet::decode_symb($env{'form.selectpage'});
 
     #get scantron line setup
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
-    my $max_bubble=&scantron_get_maxbubble();
+
+    my $navmap = Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        $r->print(&navmap_errormsg());
+        return(1,$currentphase);
+    }
+
+    my $map=$navmap->getResourceByUrl($sequence);
+    my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+    my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
+        %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
+    my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
+
+    my $nav_error;
+    if (ref($map)) {
+        $randomorder = $map->randomorder();
+        $randompick = $map->randompick();
+        if ($randomorder || $randompick) {
+            $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+            if ($nav_error) {
+                $r->print(&navmap_errormsg());
+                return(1,$currentphase);
+            }
+            &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+                                    \%grader_randomlists_by_symb,$bubbles_per_row);
+        }
+    } else {
+        $r->print(&navmap_errormsg());
+        return(1,$currentphase);
+    }
+
+
+    my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
+    if ($nav_error) {
+        $r->print(&navmap_errormsg());
+        return(1,$currentphase);
+    }
+
     if (!$max_bubble) { $max_bubble=2**31; }
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 	if ($line=~/^[\s\cz]*$/) { next; }
-	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
-						 $scan_data);
+	my $scan_record =
+            &scantron_parse_scanline($line,$i,\%scantron_config,$scan_data,undef,\%idmap,
+				     $randomorder,$randompick,$sequence,\@master_seq,
+                                     \%symb_to_resource,\%grader_partids_by_symb,
+                                     \%orderedforcode,\%respnumlookup,\%startline);
 	if (!defined($$scan_record{'scantron.missingerror'})) { next; }
 	my @to_correct;
 	
@@ -7398,9 +8527,16 @@ sub scantron_validate_missingbubbles {
             if ($missing =~ /^(\d+)\.(\d+)$/) {
                my $question = $1;
                my $subquestion = $2;
-               if (!defined($first_bubble_line{$question -1})) { next; }
-               my $first = $first_bubble_line{$question-1};
-               my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+               my ($first,$responsenum);
+               if ($randomorder || $randompick) {
+                   $responsenum = $respnumlookup{$question-1};
+                   $first = $startline{$question-1};
+               } else {
+                   $responsenum = $question-1; 
+                   $first = $first_bubble_line{$responsenum};
+               }
+               if (!defined($first)) { next; }
+               my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
                my $subcount = 1;
                while ($subcount<$subquestion) {
                    $first += $subans[$subcount-1];
@@ -7409,15 +8545,25 @@ sub scantron_validate_missingbubbles {
                my $count = $subans[$subquestion-1];
                $lastbubble = $first + $count;
             } else {
-                if (!defined($first_bubble_line{$missing - 1})) { next; }
-                $lastbubble = $first_bubble_line{$missing - 1} + $bubble_lines_per_response{$missing - 1};
+               my ($first,$responsenum);
+               if ($randomorder || $randompick) {
+                   $responsenum = $respnumlookup{$missing-1};
+                   $first = $startline{$missing-1};
+               } else {
+                   $responsenum = $missing-1;
+                   $first = $first_bubble_line{$responsenum};
+               }
+               if (!defined($first)) { next; }
+               $lastbubble = $first + $bubble_lines_per_response{$responsenum};
             }
             if ($lastbubble > $max_bubble) { next; }
 	    push(@to_correct,$missing);
 	}
 	if (@to_correct) {
 	    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
-				     $line,'missingbubble',\@to_correct);
+				     $line,'missingbubble',\@to_correct,
+                                     $randomorder,$randompick,\%respnumlookup,
+                                     \%startline);
 	    return (1,$currentphase);
 	}
 
@@ -7425,40 +8571,82 @@ sub scantron_validate_missingbubbles {
     return (0,$currentphase+1);
 }
 
+sub hand_bubble_option {
+    my (undef, undef, $sequence) =
+        &Apache::lonnet::decode_symb($env{'form.selectpage'});
+    return if ($sequence eq '');
+    my $navmap = Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        return;
+    }
+    my $needs_hand_bubbles;
+    my $map=$navmap->getResourceByUrl($sequence);
+    my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+    foreach my $res (@resources) {
+        if (ref($res)) {
+            if ($res->is_problem()) {
+                my $partlist = $res->parts();
+                foreach my $part (@{ $partlist }) {
+                    my @types = $res->responseType($part);
+                    if (grep(/^(chem|essay|image|formula|math|string|functionplot)$/,@types)) {
+                        $needs_hand_bubbles = 1;
+                        last;
+                    }
+                }
+            }
+        }
+    }
+    if ($needs_hand_bubbles) {
+        my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
+        my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
+        return &mt('The sequence to be graded contains response types which are handgraded.').'<p>'.
+               &mt('If you have already graded these by bubbling sheets to indicate points awarded, [_1]what point value is assigned to a filled last bubble in each row?','<br />').
+               '<label><input type="radio" name="scantron_lastbubblepoints" value="'.$bubbles_per_row.'" checked="checked" />'.&mt('[quant,_1,point]',$bubbles_per_row).'</label>&nbsp;'.&mt('or').'&nbsp;'.
+               '<label><input type="radio" name="scantron_lastbubblepoints" value="0" />'.&mt('0 points').'</label></p>';
+    }
+    return;
+}
 
 sub scantron_process_students {
-    my ($r) = @_;
+    my ($r,$symb) = @_;
 
     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
-    my ($symb)=&get_symb($r);
     if (!$symb) {
 	return '';
     }
     my $default_form_data=&defaultFormData($symb);
 
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
+    my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config); 
     my ($scanlines,$scan_data)=&scantron_getfile();
     my $classlist=&Apache::loncoursedata::get_classlist();
     my %idmap=&username_to_idmap($classlist);
     my $navmap=Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        $r->print(&navmap_errormsg());
+        return '';
+    }
     my $map=$navmap->getResourceByUrl($sequence);
+    my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
+        %grader_randomlists_by_symb);
+    if (ref($map)) {
+        $randomorder = $map->randomorder();
+        $randompick = $map->randompick();
+    } else {
+        $r->print(&navmap_errormsg());
+        return '';
+    }
+    my $nav_error;
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
-    my (%grader_partids_by_symb,%grader_randomlists_by_symb);
-    &graders_resources_pass(\@resources,\%grader_partids_by_symb,
-                            \%grader_randomlists_by_symb);
-    foreach my $resource (@resources) {
-        my $ressymb = $resource->symb();
-        my ($analysis,$parts) =
-            &scantron_partids_tograde($resource,$env{'request.course.id'},
-                                      $env{'user.name'},$env{'user.domain'},1);
-        $grader_partids_by_symb{$ressymb} = $parts;
-        if (ref($analysis) eq 'HASH') {
-            if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
-                $grader_randomlists_by_symb{$ressymb} = 
-                    $analysis->{'parts_withrandomlist'};
-            }
+    if ($randomorder || $randompick) {
+        $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+        if ($nav_error) {
+            $r->print(&navmap_errormsg());
+            return '';
         }
     }
+    &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+                            \%grader_randomlists_by_symb,$bubbles_per_row);
 
     my ($uname,$udom);
     my $result= <<SCANTRONFORM;
@@ -7473,17 +8661,18 @@ SCANTRONFORM
     
     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,'Bubblesheet Status',
- 				    'Bubblesheet Progress',$count,
-				    'inline',undef,'scantronupload');
-    &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
-					  'Processing first student');
+    my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
+    &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
     $r->print('<br />');
     my $start=&Time::HiRes::time();
     my $i=-1;
     my $started;
 
-    &scantron_get_maxbubble();	# Need the bubble lines array to parse.
+    &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
+    if ($nav_error) {
+        $r->print(&navmap_errormsg());
+        return '';
+    }
 
     # If an ssi failed in scantron_get_maxbubble, put an error message out to
     # the user and return.
@@ -7491,13 +8680,13 @@ SCANTRONFORM
     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.
     }
 
-    my %lettdig = &letter_to_digits();
+    my %lettdig = &Apache::lonnet::letter_to_digits();
     my $numletts = scalar(keys(%lettdig));
+    my %orderedforcode;
 
     while ($i<$scanlines->{'count'}) {
  	($uname,$udom)=('','');
@@ -7505,12 +8694,18 @@ SCANTRONFORM
  	my $line=&scantron_get_line($scanlines,$scan_data,$i);
  	if ($line=~/^[\s\cz]*$/) { next; }
 	if ($started) {
-	    &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
-						     'last student');
+	    &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
 	}
 	$started=1;
+        my %respnumlookup = ();
+        my %startline = ();
+        my $total;
  	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
- 						 $scan_data);
+                                                 $scan_data,undef,\%idmap,$randomorder,
+                                                 $randompick,$sequence,\@master_seq,
+                                                 \%symb_to_resource,\%grader_partids_by_symb,
+                                                 \%orderedforcode,\%respnumlookup,\%startline,
+                                                 \$total);
  	unless ($uname=&scantron_find_student($scan_record,$scan_data,
  					      \%idmap,$i)) {
   	    &scantron_add_delay(\@delayqueue,$line,
@@ -7522,21 +8717,55 @@ SCANTRONFORM
  				'Student '.$uname.' has multiple sheets',2);
  	    next;
  	}
+        my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
+        my $user = $uname.':'.$usec;
   	($uname,$udom)=split(/:/,$uname);
 
-        my %partids_by_symb;
-        foreach my $resource (@resources) {
-            my $ressymb = $resource->symb();
+        my $scancode;
+        if ((exists($scan_record->{'scantron.CODE'})) &&
+            (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
+            $scancode = $scan_record->{'scantron.CODE'};
+        } else {
+            $scancode = '';
+        }
+
+        my @mapresources = @resources;
+        if ($randomorder || $randompick) {
+            @mapresources = 
+                &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
+                             \%orderedforcode);
+        }
+        my (%partids_by_symb,$res_error);
+        foreach my $resource (@mapresources) {
+            my $ressymb;
+            if (ref($resource)) {
+                $ressymb = $resource->symb();
+            } else {
+                $res_error = 1;
+                last;
+            }
             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
+                my $currcode;
+                if (exists($grader_randomlists_by_symb{$ressymb})) {
+                    $currcode = $scancode;
+                }
                 my ($analysis,$parts) =
-                    &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom);
+                    &scantron_partids_tograde($resource,$env{'request.course.id'},
+                                              $uname,$udom,undef,$bubbles_per_row,
+                                              $currcode);
                 $partids_by_symb{$ressymb} = $parts;
             } else {
                 $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
             }
         }
 
+        if ($res_error) {
+            &scantron_add_delay(\@delayqueue,$line,
+                                'An error occurred while grading student '.$uname,2);
+            next;
+        }
+
 	&Apache::lonxml::clear_problem_counter();
   	&Apache::lonnet::appenv($scan_record);
 
@@ -7544,69 +8773,81 @@ SCANTRONFORM
 	    &scantron_putfile($scanlines,$scan_data);
 	}
 	
-        my $scancode;
-        if ((exists($scan_record->{'scantron.CODE'})) &&
-            (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
-            $scancode = $scan_record->{'scantron.CODE'};
-        } else {
-            $scancode = '';
-        }
-
         if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
-                                   \@resources,\%partids_by_symb) eq 'ssi_error') {
+                                   \@mapresources,\%partids_by_symb,
+                                   $bubbles_per_row,$randomorder,$randompick,
+                                   \%respnumlookup,\%startline) 
+            eq '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.
         }
 
+        if (($scancode) && ($randomorder || $randompick)) {
+            my $parmresult =
+                &Apache::lonparmset::storeparm_by_symb($symb,
+                                                       '0_examcode',2,$scancode,
+                                                       'string_examcode',$uname,
+                                                       $udom);
+        }
 	$completedstudents{$uname}={'line'=>$line};
         if ($env{'form.verifyrecord'}) {
             my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
+            if ($randompick) {
+                if ($total) {
+                    $lastpos = $total*$scantron_config{'Qlength'};
+                }
+            }
+
             my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
             chomp($studentdata);
             $studentdata =~ s/\r$//;
             my $studentrecord = '';
             my $counter = -1;
-            foreach my $resource (@resources) {
+            foreach my $resource (@mapresources) {
                 my $ressymb = $resource->symb();
                 ($counter,my $recording) =
                     &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
                                              $counter,$studentdata,$partids_by_symb{$ressymb},
-                                             \%scantron_config,\%lettdig,$numletts);
+                                             \%scantron_config,\%lettdig,$numletts,$randomorder,
+                                             $randompick,\%respnumlookup,\%startline);
                 $studentrecord .= $recording;
             }
             if ($studentrecord ne $studentdata) {
                 &Apache::lonxml::clear_problem_counter();
                 if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
-                                           \@resources,\%partids_by_symb) eq 'ssi_error') {
+                                           \@mapresources,\%partids_by_symb,
+                                           $bubbles_per_row,$randomorder,$randompick,
+                                           \%respnumlookup,\%startline) 
+                    eq '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);
                     delete($completedstudents{$uname});
                     return '';
                 }
                 $counter = -1;
                 $studentrecord = '';
-                foreach my $resource (@resources) {
+                foreach my $resource (@mapresources) {
                     my $ressymb = $resource->symb();
                     ($counter,my $recording) =
                         &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
                                                  $counter,$studentdata,$partids_by_symb{$ressymb},
-                                                 \%scantron_config,\%lettdig,$numletts);
+                                                 \%scantron_config,\%lettdig,$numletts,
+                                                 $randomorder,$randompick,\%respnumlookup,
+                                                 \%startline);
                     $studentrecord .= $recording;
                 }
                 if ($studentrecord ne $studentdata) {
-                    $r->print('<p><span class="LC_error">');
+                    $r->print('<p><span class="LC_warning">');
                     if ($scancode eq '') {
-                        $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2].',
+                        $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2].',
                                   $uname.':'.$udom,$scan_record->{'scantron.ID'}));
                     } else {
-                        $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2] and CODE: [_3].',
+                        $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2] and CODE: [_3].',
                                   $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
                     }
                     $r->print('</span><br />'.&Apache::loncommon::start_data_table()."\n".
@@ -7614,12 +8855,12 @@ SCANTRONFORM
                               '<th>'.&mt('Source').'</th><th>'.&mt('Bubbled responses').'</th>'.
                               &Apache::loncommon::end_data_table_header_row()."\n".
                               &Apache::loncommon::start_data_table_row().
-                              '<td>'.&mt('Bubble Sheet').'</td>'.
-                              '<td><span class="LC_nobreak">'.$studentdata.'</span></td>'.
+                              '<td>'.&mt('Bubblesheet').'</td>'.
+                              '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentdata.'</tt></span></td>'.
                               &Apache::loncommon::end_data_table_row().
                               &Apache::loncommon::start_data_table_row().
-                              '<td>Stored submissions</td>'.
-                              '<td><span class="LC_nobreak">'.$studentrecord.'</span></td>'."\n".
+                              '<td>'.&mt('Stored submissions').'</td>'.
+                              '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentrecord.'</tt></span></td>'."\n".
                               &Apache::loncommon::end_data_table_row().
                               &Apache::loncommon::end_data_table().'</p>');
                 } else {
@@ -7641,19 +8882,20 @@ SCANTRONFORM
 #    $r->print("<p>took $lasttime</p>");
 
     $r->print("</form>");
-    $r->print(&show_grading_menu_form($symb));
     return '';
 }
 
 sub graders_resources_pass {
-    my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb) = @_;
+    my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb,
+        $bubbles_per_row) = @_;
     if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) && 
         (ref($grader_randomlists_by_symb) eq 'HASH')) {
         foreach my $resource (@{$resources}) {
             my $ressymb = $resource->symb();
             my ($analysis,$parts) =
                 &scantron_partids_tograde($resource,$env{'request.course.id'},
-                                          $env{'user.name'},$env{'user.domain'},1);
+                                          $env{'user.name'},$env{'user.domain'},
+                                          1,$bubbles_per_row);
             $grader_partids_by_symb->{$ressymb} = $parts;
             if (ref($analysis) eq 'HASH') {
                 if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
@@ -7666,8 +8908,67 @@ sub graders_resources_pass {
     return;
 }
 
+=pod
+
+=item users_order
+
+  Returns array of resources in current map, ordered based on either CODE,
+  if this is a CODEd exam, or based on student's identity if this is a 
+  "NAMEd" exam.
+
+  Should be used when randomorder and/or randompick applied when the 
+  corresponding exam was printed, prior to students completing bubblesheets 
+  for the version of the exam the student received.
+
+=cut
+
+sub users_order  {
+    my ($user,$scancode,$mapurl,$master_seq,$symb_to_resource,$orderedforcode) = @_;
+    my @mapresources;
+    unless ((ref($master_seq) eq 'ARRAY') && (ref($symb_to_resource) eq 'HASH')) {
+        return @mapresources;
+    }
+    if ($scancode) {
+        if ((ref($orderedforcode) eq 'HASH') && (ref($orderedforcode->{$scancode}) eq 'ARRAY')) {
+            @mapresources = @{$orderedforcode->{$scancode}};
+        } else {
+            $env{'form.CODE'} = $scancode;
+            my $actual_seq =
+                &Apache::lonprintout::master_seq_to_person_seq($mapurl,
+                                                               $master_seq,
+                                                               $user,$scancode,1);
+            if (ref($actual_seq) eq 'ARRAY') {
+                @mapresources = map { $symb_to_resource->{$_}; } @{$actual_seq};
+                if (ref($orderedforcode) eq 'HASH') {
+                    if (@mapresources > 0) { 
+                        $orderedforcode->{$scancode} = \@mapresources;
+                    }
+                }
+            }
+            delete($env{'form.CODE'});
+        }
+    } else {
+        my $actual_seq =
+            &Apache::lonprintout::master_seq_to_person_seq($mapurl,
+                                                           $master_seq,
+                                                           $user,undef,1);
+        if (ref($actual_seq) eq 'ARRAY') {
+            @mapresources = 
+                map { $symb_to_resource->{$_}; } @{$actual_seq};
+        }
+    }
+    return @mapresources;
+}
+
 sub grade_student_bubbles {
-    my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts) = @_;
+    my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row,
+        $randomorder,$randompick,$respnumlookup,$startline) = @_;
+    my $uselookup = 0;
+    if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH') &&
+        (ref($startline) eq 'HASH')) {
+        $uselookup = 1;
+    }
+
     if (ref($resources) eq 'ARRAY') {
         my $count = 0;
         foreach my $resource (@{$resources}) {
@@ -7680,11 +8981,21 @@ sub grade_student_bubbles {
                         'grade_symb'     => $ressymb,
                         'CODE'           => $scancode
                        );
+            if ($bubbles_per_row ne '') {
+                $form{'bubbles_per_row'} = $bubbles_per_row;
+            }
+            if ($env{'form.scantron_lastbubblepoints'} ne '') {
+                $form{'scantron_lastbubblepoints'} = $env{'form.scantron_lastbubblepoints'};
+            }
             if (ref($parts) eq 'HASH') {
                 if (ref($parts->{$ressymb}) eq 'ARRAY') {
                     foreach my $part (@{$parts->{$ressymb}}) {
-                        $form{'scantron_questnum_start.'.$part} =
-                            1+$env{'form.scantron.first_bubble_line.'.$count};
+                        if ($uselookup) {
+                            $form{'scantron_questnum_start.'.$part} = $startline->{$count} + 1;
+                        } else {
+                            $form{'scantron_questnum_start.'.$part} =
+                                1+$env{'form.scantron.first_bubble_line.'.$count};
+                        }
                         $count++;
                     }
                 }
@@ -7698,8 +9009,9 @@ sub grade_student_bubbles {
 }
 
 sub scantron_upload_scantron_data {
-    my ($r)=@_;
+    my ($r,$symb)=@_;
     my $dom = $env{'request.role.domain'};
+    my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($dom);
     my $domdesc = &Apache::lonnet::domain($dom,'description');
     $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
@@ -7707,11 +9019,12 @@ sub scantron_upload_scantron_data {
 							  'coursename',$dom);
     my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'.
                        ('&nbsp'x2).&mt('(shows course personnel)'); 
-    my $default_form_data=&defaultFormData(&get_symb($r,1));
+    my $default_form_data=&defaultFormData($symb);
     my $nofile_alert = &mt('Please use the browse button to select a file from your local directory.');
+    &js_escape(\$nofile_alert);
     my $nocourseid_alert = &mt("Please use the 'Select Course' link to open a separate window where you can search for a course to which a file can be uploaded.");
-    $r->print('
-<script type="text/javascript" language="javascript">
+    &js_escape(\$nocourseid_alert);
+    $r->print(&Apache::lonhtmlcommon::scripttag('
     function checkUpload(formname) {
 	if (formname.upfile.value == "") {
 	    alert("'.$nofile_alert.'");
@@ -7738,9 +9051,10 @@ sub scantron_upload_scantron_data {
         return;
     }
 
-</script>
-
-<h3>'.&mt('Send scanned bubblesheet data to a course').'</h3>
+    '.$formatjs.'
+'));
+    $r->print('
+<h3>'.&mt('Send bubblesheet data to a course').'</h3>
 
 <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
 '.$default_form_data.
@@ -7753,23 +9067,105 @@ sub scantron_upload_scantron_data {
   &Apache::lonhtmlcommon::row_closure().
   &Apache::lonhtmlcommon::row_title(&mt('Domain')).
   '<input name="domainid" type="hidden" />'.$domdesc.
-  &Apache::lonhtmlcommon::row_closure().
+  &Apache::lonhtmlcommon::row_closure());
+    if ($formatoptions) {
+        $r->print(&Apache::lonhtmlcommon::row_title($formattitle).$formatoptions.
+                  &Apache::lonhtmlcommon::row_closure());
+    }
+    $r->print(
   &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
   '<input type="file" name="upfile" size="50" />'.
   &Apache::lonhtmlcommon::row_closure(1).
   &Apache::lonhtmlcommon::end_pick_box().'<br />
 
 <input name="command" value="scantronupload_save" type="hidden" />
-<input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
+<input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
 </form>
 ');
     return '';
 }
 
+sub scantron_upload_dataformat {
+    my ($dom) = @_;
+    my ($formatoptions,$formattitle,$formatjs);
+    $formatjs = <<'END';
+function toggleScantab(form) {
+   return;
+}
+END
+    my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$dom);
+    if (ref($domconfig{'scantron'}) eq 'HASH') {
+        if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
+            if (keys(%{$domconfig{'scantron'}{'config'}}) > 1) {
+                if (($domconfig{'scantron'}{'config'}{'dat'}) &&
+                    (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH')) {
+                    if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {  
+                        if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}})) {
+                            my ($onclick,$formatextra,$singleline);
+                            my @lines = &Apache::lonnet::get_scantronformat_file();
+                            my $count = 0;
+                            foreach my $line (@lines) {
+                                next if ($line =~ /^#/);
+                                $singleline = $line;
+                                $count ++;
+                            }
+                            if ($count > 1) {
+                                $formatextra = '<div style="display:none" id="bubbletype">'.
+                                               '<span class="LC_nobreak">'.
+                                               &mt('Bubblesheet type:').'&nbsp;'.
+                                               &scantron_scantab().'</span></div>';
+                                $onclick = ' onclick="toggleScantab(this.form);"';
+                                $formatjs = <<"END";
+function toggleScantab(form) {
+    var divid = 'bubbletype';
+    if (document.getElementById(divid)) {
+        var radioname = 'fileformat';
+        var num = form.elements[radioname].length;
+        if (num) {
+            for (var i=0; i<num; i++) {
+                if (form.elements[radioname][i].checked) {
+                    var chosen = form.elements[radioname][i].value;
+                    if (chosen == 'dat') {
+                        document.getElementById(divid).style.display = 'none';
+                    } else if (chosen == 'csv') {
+                        document.getElementById(divid).style.display = 'block';
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+
+END
+                            } elsif ($count == 1) {
+                                my $formatname = (split(/:/,$singleline,2))[0];
+                                $formatextra = '<input type="hidden" name="scantron_format" value="'.$formatname.'" />';
+                            }
+                            $formattitle = &mt('File format');
+                            $formatoptions = '<label><input name="fileformat" type="radio" value="dat" checked="checked"'.$onclick.' />'.
+                                             &mt('Plain Text (no delimiters)').
+                                             '</label>'.('&nbsp;'x2).
+                                             '<label><input name="fileformat" type="radio" value="csv"'.$onclick.' />'.
+                                             &mt('Comma separated values').'</label>'.$formatextra;
+                        }
+                    }
+                }
+            } elsif (keys(%{$domconfig{'scantron'}{'config'}}) == 1) {
+                if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
+                    if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}})) {
+                        $formattitle = &mt('Bubblesheet type');
+                        $formatoptions = &scantron_scantab();
+                    }
+                }
+            }
+        }
+    }
+    return ($formatoptions,$formattitle,$formatjs);
+}
 
 sub scantron_upload_scantron_data_save {
-    my($r)=@_;
-    my ($symb)=&get_symb($r,1);
+    my($r,$symb)=@_;
     my $doanotherupload=
 	'<br /><form action="/adm/grades" method="post">'."\n".
 	'<input type="hidden" name="command" value="scantronupload" />'."\n".
@@ -7779,37 +9175,72 @@ sub scantron_upload_scantron_data_save {
 	!&Apache::lonnet::allowed('usc',
 			    $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
 	$r->print(&mt("You are not allowed to upload bubblesheet data to the requested course.")."<br />");
-	if ($symb) {
-	    $r->print(&show_grading_menu_form($symb));
-	} else {
+	unless ($symb) {
 	    $r->print($doanotherupload);
 	}
 	return '';
     }
     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
     my $uploadedfile;
-    $r->print('<h3>'.&mt("Uploading file to [_1]",$coursedata{'description'}).'</h3>');
+    $r->print('<p>'.&mt('Uploading file to [_1]','"'.$coursedata{'description'}.'"').'</p>');
     if (length($env{'form.upfile'}) < 2) {
-        $r->print(&mt('[_1]Error:[_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.','<span class="LC_error">','</span>','<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
+        $r->print(
+            &Apache::lonhtmlcommon::confirm_success(
+                &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
+                        '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1));
     } else {
-        my $result = 
-            &Apache::lonnet::userfileupload('upfile','','scantron','','','',
+        my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$env{'form.domainid'});
+        my $parser;
+        if (ref($domconfig{'scantron'}) eq 'HASH') {
+            if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
+                my $is_csv;
+                my @possibles = keys(%{$domconfig{'scantron'}{'config'}});
+                if (@possibles > 1) {
+                    if ($env{'form.fileformat'} eq 'csv') {
+                        if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
+                            if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
+                                if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}}) > 1) {
+                                    $is_csv = 1;
+                                }
+                            }
+                        }
+                    }
+                } elsif (@possibles == 1) {
+                    if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
+                        if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
+                            if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}}) > 1) {
+                                $is_csv = 1;
+                            }
+                        }
+                    }
+                }
+                if ($is_csv) {
+                   $parser = $domconfig{'scantron'}{'config'}{'csv'};
+                }
+            }
+        }
+        my $result =
+            &Apache::lonnet::userfileupload('upfile','scantron','scantron',$parser,'','',
                                             $env{'form.courseid'},$env{'form.domainid'});
-	if ($result =~ m{^/uploaded/}) {
-	    $r->print(&mt('[_1]Success:[_2] Successfully uploaded [_3] bytes of data into location: [_4]',
-                          '<span class="LC_success">','</span>',(length($env{'form.upfile'})-1),
-			  '<span class="LC_filename">'.$result.'</span>'));
+        if ($result =~ m{^/uploaded/}) {
+            $r->print(
+                &Apache::lonhtmlcommon::confirm_success(&mt('Upload successful')).'<br />'.
+                &mt('Uploaded [_1] bytes of data into location: [_2]',
+                        (length($env{'form.upfile'})-1),
+                        '<span class="LC_filename">'.$result.'</span>'));
             ($uploadedfile) = ($result =~ m{/([^/]+)$});
             $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
                                                        $env{'form.courseid'},$uploadedfile));
-	} else {
-	    $r->print(&mt('[_1]Error:[_2] An error ([_3]) occurred when attempting to upload the file, [_4]',
-                          '<span class="LC_error">','</span>',$result,
+        } else {
+            $r->print(
+                &Apache::lonhtmlcommon::confirm_success(&mt('Upload failed'),1).'<br />'.
+                    &mt('An error ([_1]) occurred when attempting to upload the file: [_2]',
+                          $result,
 			  '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
 	}
     }
     if ($symb) {
-	$r->print(&scantron_selectphase($r,$uploadedfile));
+	$r->print(&scantron_selectphase($r,$uploadedfile,$symb));
     } else {
 	$r->print($doanotherupload);
     }
@@ -7826,7 +9257,7 @@ sub validate_uploaded_scantron_file {
     my $output;
     if (@lines) {
         my (%counts,$max_match_format);
-        my ($max_match_count,$max_match_pct) = (0,0);
+        my ($found_match_count,$max_match_count,$max_match_pct) = (0,0,0);
         my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
         my %idmap = &username_to_idmap($classlist);
         foreach my $key (keys(%idmap)) {
@@ -7834,7 +9265,7 @@ sub validate_uploaded_scantron_file {
             $idmap{$lckey} = $idmap{$key};
         }
         my %unique_formats;
-        my @formatlines = &get_scantronformat_file();
+        my @formatlines = &Apache::lonnet::get_scantronformat_file();
         foreach my $line (@formatlines) {
             chomp($line);
             my @config = split(/:/,$line);
@@ -7869,6 +9300,7 @@ sub validate_uploaded_scantron_file {
                 if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
                     $max_match_pct = $percent_match;
                     $max_match_format = $key;
+                    $found_match_count = $counts{$key}{'found'};
                     $max_match_count = $counts{$key}{'total'};
                 }
             }
@@ -7887,17 +9319,40 @@ sub validate_uploaded_scantron_file {
                 }
             }
             my $showpct = sprintf("%.0f",$max_match_pct).'%';
-            $output .= '<br />'.&mt('Comparison of student IDs in the uploaded file with the course roster found matches for [_1] of the [_2] entries in the file (for the format defined for [_3]).','<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
-                       '<br />'.&mt('A low percentage of matches results from one of the following:').'<ul>'.
-                       '<li>'.&mt('The file was uploaded to the wrong course').'</li>'.
-                       '<li>'.&mt('The data are not in the format expected for the domain: [_1]',
-                                  '<i>'.$cdom.'</i>').'</li>'.
-                       '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
-                       '<li>'.&mt('The course roster is not up to date').'</li>'.
-                       '</ul>';
+            $output .= '<br />';
+            if ($found_match_count == $max_match_count) {
+                # 100% matching entries
+                $output .= &Apache::lonhtmlcommon::confirm_success(
+                     &mt('Comparison of student IDs: [_1] matching ([quant,_2,entry,entries])',
+                            '<b>'.$showpct.'</b>',$found_match_count)).'<br />'.
+                &mt('Comparison of student IDs in the uploaded file with'.
+                    ' the course roster found matches for [_1] of the [_2] entries'.
+                    ' in the file (for the format defined for [_3]).',
+                        '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs);
+            } else {
+                # Not all entries matching? -> Show warning and additional info
+                $output .=
+                    &Apache::lonhtmlcommon::confirm_success(
+                        &mt('Comparison of student IDs: [_1] matching ([_2]/[quant,_3,entry,entries])',
+                                '<b>'.$showpct.'</b>',$found_match_count,$max_match_count).'<br />'.
+                        &mt('Not all entries could be matched!'),1).'<br />'.
+                    &mt('Comparison of student IDs in the uploaded file with'.
+                        ' the course roster found matches for [_1] of the [_2] entries'.
+                        ' in the file (for the format defined for [_3]).',
+                            '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
+                    '<p class="LC_info">'.
+                    &mt('A low percentage of matches results from one of the following:').
+                    '</p><ul>'.
+                    '<li>'.&mt('The file was uploaded to the wrong course.').'</li>'.
+                    '<li>'.&mt('The data is not in the format expected for the domain: [_1]',
+                               '<i>'.$cdom.'</i>').'</li>'.
+                    '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
+                    '<li>'.&mt('The course roster is not up to date.').'</li>'.
+                    '</ul>';
+            }
         }
     } else {
-        $output = '<span class="LC_warning">'.&mt('Uploaded file contained no data').'</span>';
+        $output = '<p class="LC_warning">'.&mt('Uploaded file contained no data').'</p>';
     }
     return $output;
 }
@@ -7911,18 +9366,17 @@ sub valid_file {
 }
 
 sub scantron_download_scantron_data {
-    my ($r)=@_;
-    my $default_form_data=&defaultFormData(&get_symb($r,1));
+    my ($r,$symb)=@_;
+    my $default_form_data=&defaultFormData($symb);
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $file=$env{'form.scantron_selectfile'};
     if (! &valid_file($file)) {
 	$r->print('
 	<p>
-	    '.&mt('The requested file name was invalid.').'
+	    '.&mt('The requested filename was invalid.').'
         </p>
 ');
-	$r->print(&show_grading_menu_form(&get_symb($r,1)));
 	return;
     }
     my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
@@ -7933,7 +9387,7 @@ sub scantron_download_scantron_data {
     &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
     $r->print('
     <p>
-	'.&mt('[_1]Original[_2] file as uploaded by the scantron office.',
+	'.&mt('[_1]Original[_2] file as uploaded by the bubblesheet scanning office.',
 	      '<a href="'.$orig.'">','</a>').'
     </p>
     <p>
@@ -7945,33 +9399,45 @@ sub scantron_download_scantron_data {
 	      '<a href="'.$skipped.'">','</a>').'
     </p>
 ');
-    $r->print(&show_grading_menu_form(&get_symb($r,1)));
     return '';
 }
 
 sub checkscantron_results {
-    my ($r) = @_;
-    my ($symb)=&get_symb($r);
+    my ($r,$symb) = @_;
     if (!$symb) {return '';}
-    my $grading_menu_button=&show_grading_menu_form($symb);
     my $cid = $env{'request.course.id'};
-    my %lettdig = &letter_to_digits();
+    my %lettdig = &Apache::lonnet::letter_to_digits();
     my $numletts = scalar(keys(%lettdig));
     my $cnum = $env{'course.'.$cid.'.num'};
     my $cdom = $env{'course.'.$cid.'.domain'};
     my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
     my %record;
     my %scantron_config =
-        &Apache::grades::get_scantron_config($env{'form.scantron_format'});
+        &Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
+    my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
     my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
     my $classlist=&Apache::loncoursedata::get_classlist();
     my %idmap=&Apache::grades::username_to_idmap($classlist);
     my $navmap=Apache::lonnavmaps::navmap->new();
+    unless (ref($navmap)) {
+        $r->print(&navmap_errormsg());
+        return '';
+    }
     my $map=$navmap->getResourceByUrl($sequence);
+    my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
+        %grader_randomlists_by_symb,%orderedforcode);
+    if (ref($map)) { 
+        $randomorder=$map->randomorder();
+        $randompick=$map->randompick();
+    }
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
-    my (%grader_partids_by_symb,%grader_randomlists_by_symb);
-    &graders_resources_pass(\@resources,\%grader_partids_by_symb,                             \%grader_randomlists_by_symb);
-
+    my $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+    if ($nav_error) {
+        $r->print(&navmap_errormsg());
+        return '';
+    }
+    &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+                            \%grader_randomlists_by_symb,$bubbles_per_row);
     my ($uname,$udom);
     my (%scandata,%lastname,%bylast);
     $r->print('
@@ -7980,16 +9446,16 @@ sub checkscantron_results {
     my @delayqueue;
     my %completedstudents;
 
-    my $count=&Apache::grades::get_todo_count($scanlines,$scan_data);
-    my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Bubblesheet/Submissions Comparison Status',
-                                    'Progress of Bubblesheet Data/Submission Records Comparison',$count,
-                                    'inline',undef,'checkscantron');
+    my $count=&get_todo_count($scanlines,$scan_data);
+    my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
     my ($username,$domain,$started);
+    &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
+    if ($nav_error) {
+        $r->print(&navmap_errormsg());
+        return '';
+    }
 
-    &scantron_get_maxbubble();  # Need the bubble lines array to parse.
-
-    &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
-                                          'Processing first student');
+    &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
     my $start=&Time::HiRes::time();
     my $i=-1;
 
@@ -7999,15 +9465,14 @@ sub checkscantron_results {
         my $line=&Apache::grades::scantron_get_line($scanlines,$scan_data,$i);
         if ($line=~/^[\s\cz]*$/) { next; }
         if ($started) {
-            &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
-                                                     'last student');
+            &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
         }
         $started=1;
         my $scan_record=
             &Apache::grades::scantron_parse_scanline($line,$i,\%scantron_config,
                                                      $scan_data);
-        unless ($uname=&Apache::grades::scantron_find_student($scan_record,$scan_data,
-                                                              \%idmap,$i)) {
+        unless ($uname=&scantron_find_student($scan_record,$scan_data,
+                                              \%idmap,$i)) {
             &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                                 'Unable to find a student that matches',1);
             next;
@@ -8020,26 +9485,61 @@ sub checkscantron_results {
         my $pid = $scan_record->{'scantron.ID'};
         $lastname{$pid} = $scan_record->{'scantron.LastName'};
         push(@{$bylast{$lastname{$pid}}},$pid);
+        my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
+        my $user = $uname.':'.$usec;
+        ($username,$domain)=split(/:/,$uname);
+
+        my $scancode;
+        if ((exists($scan_record->{'scantron.CODE'})) &&
+            (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
+            $scancode = $scan_record->{'scantron.CODE'};
+        } else {
+            $scancode = '';
+        }
+
+        my @mapresources = @resources;
         my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
+        my %respnumlookup=();
+        my %startline=();
+        if ($randomorder || $randompick) {
+            @mapresources =
+                &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
+                             \%orderedforcode);
+            my $total = &get_respnum_lookups($sequence,$scan_data,\%idmap,$line,
+                                             $scan_record,\@master_seq,\%symb_to_resource,
+                                             \%grader_partids_by_symb,\%orderedforcode,
+                                             \%respnumlookup,\%startline);
+            if ($randompick && $total) {
+                $lastpos = $total*$scantron_config{'Qlength'};
+            }
+        }
         $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
         chomp($scandata{$pid});
         $scandata{$pid} =~ s/\r$//;
-        ($username,$domain)=split(/:/,$uname);
+
         my $counter = -1;
-        foreach my $resource (@resources) {
+        foreach my $resource (@mapresources) {
             my $parts;
             my $ressymb = $resource->symb();
             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
+                my $currcode;
+                if (exists($grader_randomlists_by_symb{$ressymb})) {
+                    $currcode = $scancode;
+                }
                 (my $analysis,$parts) =
-                    &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain);
+                    &scantron_partids_tograde($resource,$env{'request.course.id'},
+                                              $username,$domain,undef,
+                                              $bubbles_per_row,$currcode);
             } else {
                 $parts = $grader_partids_by_symb{$ressymb};
             }
             ($counter,my $recording) =
                 &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
                                          $scandata{$pid},$parts,
-                                         \%scantron_config,\%lettdig,$numletts);
+                                         \%scantron_config,\%lettdig,$numletts,
+                                         $randomorder,$randompick,
+                                         \%respnumlookup,\%startline);
             $record{$pid} .= $recording;
         }
     }
@@ -8062,14 +9562,14 @@ sub checkscantron_results {
 '<td>'.&mt('Bubblesheet').'</td><td>'.$showscandata.'</td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
 '</tr>'."\n".
 '<tr class="'.$css_class.'">'."\n".
-'<td>Submissions</td><td>'.$showrecord.'</td></tr>'."\n";
+'<td>'.&mt('Submissions').'</td><td>'.$showrecord.'</td></tr>'."\n";
                     $passed ++;
                 } else {
                     my $css_class = ($failed % 2)?'LC_odd_row':'LC_even_row';
                     $badstudents .= '<tr class="'.$css_class.'"><td>'.&mt('Bubblesheet').'</td><td><span class="LC_nobreak">'.$scandata{$pid}.'</span></td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
 '</tr>'."\n".
 '<tr class="'.$css_class.'">'."\n".
-'<td>Submissions</td><td><span class="LC_nobreak">'.$record{$pid}.'</span></td>'."\n".
+'<td>'.&mt('Submissions').'</td><td><span class="LC_nobreak">'.$record{$pid}.'</span></td>'."\n".
 '</tr>'."\n";
                     $failed ++;
                 }
@@ -8077,8 +9577,21 @@ sub checkscantron_results {
             }
         }
     }
-    $r->print('<p>'.&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for <b>[quant,_1,student]</b>  ([_2] scantron lines/student).',$numstudents,$env{'form.scantron_maxbubble'}).'</p>');
-    $r->print('<p>'.&mt('Exact matches for <b>[quant,_1,student]</b>.',$passed).'<br />'.&mt('Discrepancies detected for <b>[quant,_1,student]</b>.',$failed).'</p>');
+    $r->print(
+        '<p>'
+       .&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for [_1][quant,_2,student][_3] ([quant,_4,bubblesheet line] per student).',
+            '<b>',
+            $numstudents,
+            '</b>',
+            $env{'form.scantron_maxbubble'})
+       .'</p>'
+    );
+    $r->print('<p>'
+             .&mt('Exact matches for [_1][quant,_2,student][_3].','<b>',$passed,'</b>')
+             .'<br />'
+             .&mt('Discrepancies detected for [_1][quant,_2,student][_3].','<b>',$failed,'</b>')
+             .'</p>'
+    );
     if ($passed) {
         $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').'<br /><br />');
         $r->print(&Apache::loncommon::start_data_table()."\n".
@@ -8098,13 +9611,14 @@ sub checkscantron_results {
                  &Apache::loncommon::end_data_table()).'<br />'.
                  &mt('Differences can occur if submissions were modified using manual grading after a bubblesheet grading pass.').'<br />'.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original bubblesheets.');  
     }
-    $r->print('</form><br />'.$grading_menu_button);
+    $r->print('</form><br />');
     return;
 }
 
 sub verify_scantron_grading {
     my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
-        $scantron_config,$lettdig,$numletts) = @_;
+        $scantron_config,$lettdig,$numletts,$randomorder,$randompick,
+        $respnumlookup,$startline) = @_;
     my ($record,%expected,%startpos);
     return ($counter,$record) if (!ref($resource));
     return ($counter,$record) if (!$resource->is_problem());
@@ -8113,15 +9627,21 @@ sub verify_scantron_grading {
     foreach my $part_id (@{$partids}) {
         $counter ++;
         $expected{$part_id} = 0;
-        if ($env{"form.scantron.sub_bubblelines.$counter"}) {
-            my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$counter"});
+        my $respnum = $counter;
+        if ($randomorder || $randompick) {
+            $respnum = $respnumlookup->{$counter};
+            $startpos{$part_id} = $startline->{$counter} + 1;
+        } else {
+            $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
+        }
+        if ($env{"form.scantron.sub_bubblelines.$respnum"}) {
+            my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$respnum"});
             foreach my $item (@sub_lines) {
                 $expected{$part_id} += $item;
             }
         } else {
-            $expected{$part_id} = $env{"form.scantron.bubblelines.$counter"};
+            $expected{$part_id} = $env{"form.scantron.bubblelines.$respnum"};
         }
-        $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
     }
     if ($symb) {
         my %recorded;
@@ -8217,22 +9737,6 @@ sub verify_scantron_grading {
     return ($counter,$record);
 }
 
-sub letter_to_digits { 
-    my %lettdig = (
-                    A => 1,
-                    B => 2,
-                    C => 3,
-                    D => 4,
-                    E => 5,
-                    F => 6,
-                    G => 7,
-                    H => 8,
-                    I => 9,
-                    J => 0,
-                  );
-    return %lettdig;
-}
-
 
 #-------- end of section for handling grading scantron forms -------
 #
@@ -8240,321 +9744,278 @@ sub letter_to_digits {
 
 #-------------------------- Menu interface -------------------------
 #
-#--- Show a Grading Menu button - Calls the next routine ---
-sub show_grading_menu_form {
-    my ($symb)=@_;
-    my $result.='<br /><form action="/adm/grades" method="post">'."\n".
-	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
-	'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
-	'<input type="hidden" name="command" value="gradingmenu" />'."\n".
-	'<input type="submit" name="submit" value="'.&mt('Grading Menu').'" />'."\n".
-	'</form>'."\n";
-    return $result;
-}
+#--- Href with symb and command ---
 
-# -- Retrieve choices for grading form
-sub savedState {
-    my %savedState = ();
-    if ($env{'form.saveState'}) {
-	foreach (split(/:/,$env{'form.saveState'})) {
-	    my ($key,$value) = split(/=/,$_,2);
-	    $savedState{$key} = $value;
-	}
-    }
-    return \%savedState;
+sub href_symb_cmd {
+    my ($symb,$cmd)=@_;
+    return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&amp;command='.$cmd;
 }
 
 sub grading_menu {
-    my ($request) = @_;
-    my ($symb)=&get_symb($request);
+    my ($request,$symb) = @_;
     if (!$symb) {return '';}
-    my $probTitle = &Apache::lonnet::gettitle($symb);
-    my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
 
-    $request->print($table);
     my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
-                  'handgrade'=>$hdgrade,
-                  'probTitle'=>$probTitle,
-                  'command'=>'submit_options',
-                  'saveState'=>"",
-                  'gradingMenu'=>1,
-                  'showgrading'=>"yes");
-    my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
-    my @menu = ({ url => $url,
-                     name => &mt('Manual Grading/View Submissions'),
-                     short_description =>
-    &mt('Start the process of hand grading submissions.'),
-                 });
+                  'command'=>'individual');
+    
+    my $url1a = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+    $fields{'command'}='ungraded';
+    my $url1b=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+    $fields{'command'}='table';
+    my $url1c=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+    $fields{'command'}='all_for_one';
+    my $url1d=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+    $fields{'command'}='downloadfilesselect';
+    my $url1e=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
     $fields{'command'} = 'csvform';
-    $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
-    push(@menu, { url => $url,
-                   name => &mt('Upload Scores'),
-                   short_description =>
-            &mt('Specify a file containing the class scores for current resource.')});
+    my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+    
     $fields{'command'} = 'processclicker';
-    $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
-    push(@menu, { url => $url,
-                   name => &mt('Process Clicker'),
-                   short_description =>
-            &mt('Specify a file containing the clicker information for this resource.')});
+    my $url3 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+    
     $fields{'command'} = 'scantron_selectphase';
-    $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
-    push(@menu, { url => $url,
-                   name => &mt('Grade/Manage/Review Bubblesheets'),
-                   short_description =>
-            &mt('Grade scantron exams, upload/download scantron data files, and review previously graded scantron exams.')});
-    $fields{'command'} = 'verify';
-    $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
-    push(@menu, { url => "",
-                   name => &mt('Verify Receipt'),
-                   short_description =>
-            &mt('')});
+    my $url4 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+    $fields{'command'} = 'initialverifyreceipt';
+    my $url5 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+    
+    my @menu = ({	categorytitle=>'Hand Grading',
+            items =>[
+                        {	linktext => 'Select individual students to grade',
+                    		url => $url1a,
+                    		permission => 'F',
+                    		icon => 'grade_students.png',
+                    		linktitle => 'Grade current resource for a selection of students.'
+                        }, 
+                        {       linktext => 'Grade ungraded submissions.',
+                                url => $url1b,
+                                permission => 'F',
+                                icon => 'ungrade_sub.png',
+                                linktitle => 'Grade all submissions that have not been graded yet.'
+                        },
+
+                        {       linktext => 'Grading table',
+                                url => $url1c,
+                                permission => 'F',
+                                icon => 'grading_table.png',
+                                linktitle => 'Grade current resource for all students.'
+                        },
+                        {       linktext => 'Grade page/folder for one student',
+                                url => $url1d,
+                                permission => 'F',
+                                icon => 'grade_PageFolder.png',
+                                linktitle => 'Grade all resources in current page/sequence/folder for one student.'
+                        },
+                        {       linktext => 'Download submissions',
+                                url => $url1e,
+                                permission => 'F',
+                                icon => 'download_sub.png',
+                                linktitle => 'Download all students submissions.'
+                        }]},
+                         { categorytitle=>'Automated Grading',
+               items =>[
+
+                	    {	linktext => 'Upload Scores',
+                    		url => $url2,
+                    		permission => 'F',
+                    		icon => 'uploadscores.png',
+                    		linktitle => 'Specify a file containing the class scores for current resource.'
+                	    },
+                	    {	linktext => 'Process Clicker',
+                    		url => $url3,
+                    		permission => 'F',
+                    		icon => 'addClickerInfoFile.png',
+                    		linktitle => 'Specify a file containing the clicker information for this resource.'
+                	    },
+                	    {	linktext => 'Grade/Manage/Review Bubblesheets',
+                    		url => $url4,
+                    		permission => 'F',
+                    		icon => 'bubblesheet.png',
+                    		linktitle => 'Grade bubblesheet exams, upload/download bubblesheet data files, and review previously graded bubblesheet exams.'
+                	    },
+                            {   linktext => 'Verify Receipt Number',
+                                url => $url5,
+                                permission => 'F',
+                                icon => 'receipt_number.png',
+                                linktitle => 'Verify a system-generated receipt number for correct problem solution.'
+                            }
+
+                    ]
+            });
+
     # Create the menu
     my $Str;
-    # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>';
     $Str .= '<form method="post" action="" name="gradingMenu">';
     $Str .= '<input type="hidden" name="command" value="" />'.
-    	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
-	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
-	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
-	'<input type="hidden" name="saveState"   value="" />'."\n".
-	'<input type="hidden" name="gradingMenu" value="1" />'."\n".
-	'<input type="hidden" name="showgrading" value="yes" />'."\n";
-    foreach my $menudata (@menu) {
-        if ($menudata->{'name'} ne &mt('Verify Receipt')) {
-            $Str .='    <h3><a '.
-                $menudata->{'jscript'}.
-                ' href="'.
-                $menudata->{'url'}.'" >'.
-                $menudata->{'name'}."</a></h3>\n";
-        } else {
-            $Str .='<hr /><input type="button" value="'.&mt('Verify Receipt').'" '.
-                $menudata->{'jscript'}.
-                ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
-                ' /> '.
-                &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";
-    }
-    $Str .="</form>\n";
-    my $receiptalert = &mt("Please enter a receipt number given by a student in the receipt box.");
-    $request->print(<<GRADINGMENUJS);
-<script type="text/javascript" language="javascript">
-    function checkChoice(formname,val,cmdx) {
-	if (val <= 2) {
-	    var cmd = radioSelection(formname.radioChoice);
-	    var cmdsave = cmd;
-	} else {
-	    cmd = cmdx;
-	    cmdsave = 'submission';
-	}
-	formname.command.value = cmd;
-	if (val < 5) formname.submit();
-	if (val == 5) {
-	    if (!checkReceiptNo(formname,'notOK')) { 
-	        return false;
-	    } else {
-	        formname.submit();
-	    }
-	}
-    }
+    	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
 
-    function checkReceiptNo(formname,nospace) {
-	var receiptNo = formname.receipt.value;
-	var checkOpt = false;
-	if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
-	if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
-	if (checkOpt) {
-	    alert("$receiptalert");
-	    formname.receipt.value = "";
-	    formname.receipt.focus();
-	    return false;
-	}
-	return true;
-    }
-</script>
-GRADINGMENUJS
-    &commonJSfunctions($request);
+    $Str .= &Apache::lonhtmlcommon::generate_menu(@menu);
     return $Str;    
 }
 
 
-#--- Displays the submissions first page -------
-sub submit_options {
-    my ($request) = @_;
-    my ($symb)=&get_symb($request);
+sub ungraded {
+    my ($request)=@_;
+    &submit_options($request);
+}
+
+sub submit_options_sequence {
+    my ($request,$symb) = @_;
     if (!$symb) {return '';}
-    my $probTitle = &Apache::lonnet::gettitle($symb);
+    &commonJSfunctions($request);
+    my $result;
 
-    my $receiptalert = &mt("Please enter a receipt number given by a student in the receipt box."); 
-    $request->print(<<GRADINGMENUJS);
-<script type="text/javascript" language="javascript">
-    function checkChoice(formname,val,cmdx) {
-	if (val <= 2) {
-	    var cmd = radioSelection(formname.radioChoice);
-	    var cmdsave = cmd;
-	} else {
-	    cmd = cmdx;
-	    cmdsave = 'submission';
-	}
-	formname.command.value = cmd;
-	formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
-	    ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
-	if (val < 5) formname.submit();
-	if (val == 5) {
-	    if (!checkReceiptNo(formname,'notOK')) { return false;}
-	    formname.submit();
-	}
-	if (val < 7) formname.submit();
-    }
-
-    function checkReceiptNo(formname,nospace) {
-	var receiptNo = formname.receipt.value;
-	var checkOpt = false;
-	if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
-	if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
-	if (checkOpt) {
-	    alert("$receiptalert");
-	    formname.receipt.value = "";
-	    formname.receipt.focus();
-	    return false;
-	}
-	return true;
-    }
-</script>
-GRADINGMENUJS
+    $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
+        '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
+    $result.=&selectfield(0).
+            '<input type="hidden" name="command" value="pickStudentPage" />
+            <div>
+              <input type="submit" value="'.&mt('Next').' &rarr;" />
+            </div>
+        </div>
+  </form>';
+    return $result;
+}
+
+sub submit_options_table {
+    my ($request,$symb) = @_;
+    if (!$symb) {return '';}
     &commonJSfunctions($request);
-    my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
+    my $is_tool = ($symb =~ /ext\.tool$/);
     my $result;
-    my (undef,$sections) = &getclasslist('all','0');
-    my $savedState = &savedState();
-    my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});
-    my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'});
-    my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});
-    my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
-
-    # Preselect sections
-    my $selsec="";
-    if (ref($sections)) {
-        foreach my $section (sort(@$sections)) {
-            $selsec.='<option value="'.$section.'" '.
-                ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";
-        }
-    }
 
     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
-	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
-	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
-	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
-	'<input type="hidden" name="command"     value="" />'."\n".
-	'<input type="hidden" name="saveState"   value="" />'."\n".
-	'<input type="hidden" name="gradingMenu" value="1" />'."\n".
-	'<input type="hidden" name="showgrading" value="yes" />'."\n";
+        '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
 
-    $result.='
-    <div class="LC_grade_select_mode">
-      <div class="LC_grade_select_mode_current">
-        <h2>
-          '.&mt('Grade Current Resource').'
-        </h2>
-        <div class="LC_grade_select_mode_body">
-          <div class="LC_grades_resource_info">
-           '.$table.'
-          </div>
-          <div class="LC_grade_select_mode_selector">
-             <div class="LC_grade_select_mode_selector_header">
-                '.&mt('Sections').'
-             </div>
-             <div class="LC_grade_select_mode_selector_body">
-               <select name="section" multiple="multiple" size="5">'."\n";
-    if (ref($sections)) {
-        foreach my $section (sort(@$sections)) {
-            $result.='<option value="'.$section.'" '.
-                ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";
-        }
-    }
-    $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
-    $result.='
-             </div>
-          </div>
-          <div class="LC_grade_select_mode_selector">
-             <div class="LC_grade_select_mode_selector_header">
-                '.&mt('Groups').'
-             </div>
-             <div class="LC_grade_select_mode_selector_body">
-                '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
-             </div>
-          </div>
-          <div class="LC_grade_select_mode_selector">
-             <div class="LC_grade_select_mode_selector_header">
-                '.&mt('Access Status').'
-             </div>
-             <div class="LC_grade_select_mode_selector_body">
-                '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'
-             </div>
-          </div>
-          <div class="LC_grade_select_mode_selector">
-             <div class="LC_grade_select_mode_selector_header">
-                '.&mt('Submission Status').'
-             </div>
-             <div class="LC_grade_select_mode_selector_body">
-               <select name="submitonly" size="5">
-                 <option value="yes" '.      ($saveSub eq 'yes'       ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>
-                 <option value="queued" '.   ($saveSub eq 'queued'    ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>
-                 <option value="graded" '.   ($saveSub eq 'graded'    ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>
-                 <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>
-                 <option value="all" '.      ($saveSub eq 'all'       ? 'selected="selected"' : '').'>'.&mt('with any status').'</option>
-               </select>
-             </div>
-          </div>
-          <div class="LC_grade_select_mode_type_body">
-            <div class="LC_grade_select_mode_type">
-              <label>
-                <input type="radio" name="radioChoice" value="submission" '.
-                  ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.
-             &mt('Select individual students to grade and view submissions.').'
-              </label>
+    $result.=&selectfield(1,$is_tool).
+            '<input type="hidden" name="command" value="viewgrades" />
+            <div>
+              <input type="submit" value="'.&mt('Next').' &rarr;" />
             </div>
-            <div class="LC_grade_select_mode_type">
-              <label>
-                <input type="radio" name="radioChoice" value="viewgrades" '.
-                  ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
-                    &mt('Grade all selected students in a grading table.').'
-              </label>
-            </div>
-            <div class="LC_grade_select_mode_type">
-              <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next-&gt;').'" />
-            </div>
-          </div>
         </div>
-      </div>
-      <div class="LC_grade_select_mode_page">
-        <h2>
-         '.&mt('Grade Complete Folder for One Student').'
-        </h2>
-        <div class="LC_grades_select_mode_body">
-          <div class="LC_grade_select_mode_type_body">
-            <div class="LC_grade_select_mode_type">
-              <label>
-                <input type="radio" name="radioChoice" value="pickStudentPage" '.
-          ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
-  &mt('The <b>complete</b> page/sequence/folder: For one student').'
-              </label>
+  </form>';
+    return $result;
+}
+
+sub submit_options_download {
+    my ($request,$symb) = @_;
+    if (!$symb) {return '';}
+
+    my $is_tool = ($symb =~ /ext\.tool$/);
+    &commonJSfunctions($request);
+
+    my $result='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
+        '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
+    $result.='
+<h2>
+  '.&mt('Select Students for whom to Download Submissions').'
+</h2>'.&selectfield(1,$is_tool).'
+                <input type="hidden" name="command" value="downloadfileslink" /> 
+              <input type="submit" value="'.&mt('Next').' &rarr;" />
             </div>
-            <div class="LC_grade_select_mode_type">
-              <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next-&gt;').'" />
+          </div>
+
+
+  </form>';
+    return $result;
+}
+
+#--- Displays the submissions first page -------
+sub submit_options {
+    my ($request,$symb) = @_;
+    if (!$symb) {return '';}
+
+    my $is_tool = ($symb =~ /ext\.tool$/);
+    &commonJSfunctions($request);
+    my $result;
+
+    $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
+	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
+    $result.=&selectfield(1,$is_tool).'
+                <input type="hidden" name="command" value="submission" /> 
+	      <input type="submit" value="'.&mt('Next').' &rarr;" />
             </div>
           </div>
-        </div>
-      </div>
-    </div>
+
+
   </form>';
-    $result .= &show_grading_menu_form($symb);
     return $result;
 }
 
+sub selectfield {
+   my ($full,$is_tool)=@_;
+   my %options;
+   if ($is_tool) {
+       %options =
+           (&transtatus_options,
+            'select_form_order' => ['yes','incorrect','all']);
+   } else {
+       %options = 
+           (&substatus_options,
+            'select_form_order' => ['yes','queued','graded','incorrect','all']);
+   }
+   my $result='<div class="LC_columnSection">
+  
+    <fieldset>
+      <legend>
+       '.&mt('Sections').'
+      </legend>
+      '.&Apache::lonstatistics::SectionSelect('section','multiple',5).'
+    </fieldset>
+  
+    <fieldset>
+      <legend>
+        '.&mt('Groups').'
+      </legend>
+      '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
+    </fieldset>
+  
+    <fieldset>
+      <legend>
+        '.&mt('Access Status').'
+      </legend>
+      '.&Apache::lonhtmlcommon::StatusOptions(undef,undef,5,undef,'mult').'
+    </fieldset>';
+    if ($full) {
+        my $heading = &mt('Submission Status');
+        if ($is_tool) {
+            $heading = &mt('Transaction Status');
+        }
+        $result.='
+    <fieldset>
+      <legend>
+        '.$heading.'
+      </legend>'.
+       &Apache::loncommon::select_form('all','submitonly',\%options).
+   '</fieldset>';
+    }
+    $result.='</div><br />';
+    return $result;
+}
+
+sub substatus_options {
+    return &Apache::lonlocal::texthash(
+                                      'yes'       => 'with submissions',
+                                      'queued'    => 'in grading queue',
+                                      'graded'    => 'with ungraded submissions',
+                                      'incorrect' => 'with incorrect submissions',
+                                      'all'       => 'with any status',
+                                      );
+}
+
+sub transtatus_options {
+    return &Apache::lonlocal::texthash(
+                                       'yes'       => 'with score transactions',
+                                       'incorrect' => 'with less than full credit',
+                                       'all'       => 'with any status',
+                                      );
+}
+
 sub reset_perm {
     undef(%perm);
 }
@@ -8577,6 +10038,21 @@ sub init_perm {
     }
 }
 
+sub init_old_essays {
+    my ($symb,$apath,$adom,$aname) = @_;
+    if ($symb ne '') {
+        my %essays = &Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
+        if (keys(%essays) > 0) {
+            $old_essays{$symb} = \%essays;
+        }
+    }
+    return;
+}
+
+sub reset_old_essays {
+    undef(%old_essays);
+}
+
 sub gather_clicker_ids {
     my %clicker_ids;
 
@@ -8639,18 +10115,14 @@ sub clicker_grading_parameters {
 }
 
 sub process_clicker {
-    my ($r)=@_;
-    my ($symb)=&get_symb($r);
+    my ($r,$symb)=@_;
     if (!$symb) {return '';}
     my $result=&checkforfile_js();
-    $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
-    my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
-    $result.=$table;
-    $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
-    $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
-    $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource.').
-        '</b></td></tr>'."\n";
-    $result.='<tr bgcolor=#ffffe6><td>'."\n";
+    $result.=&Apache::loncommon::start_data_table().
+             &Apache::loncommon::start_data_table_header_row().
+             '<th>'.&mt('Specify a file containing clicker information and set grading options.').'</th>'.
+             &Apache::loncommon::end_data_table_header_row().
+             &Apache::loncommon::start_data_table_row()."<td>\n";
 # Attempt to restore parameters from last session, set defaults if not present
     my %Saveable_Parameters=&clicker_grading_parameters();
     &Apache::loncommon::restore_course_settings('grades_clicker',
@@ -8667,7 +10139,7 @@ sub process_clicker {
        }
     }
 
-    my $upload=&mt("Upload File");
+    my $upload=&mt("Evaluate File");
     my $type=&mt("Type");
     my $attendance=&mt("Award points just for participation");
     my $personnel=&mt("Correctness determined from response by course personnel");
@@ -8677,11 +10149,11 @@ sub process_clicker {
     my $pcorrect=&mt("Percentage points for correct solution");
     my $pincorrect=&mt("Percentage points for incorrect solution");
     my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
-						   ('iclicker' => 'i>clicker',
-                                                    'interwrite' => 'interwrite PRS'));
+						   {'iclicker' => 'i>clicker',
+                                                    'interwrite' => 'interwrite PRS',
+                                                    'turning' => 'Turning Technologies'});
     $symb = &Apache::lonenc::check_encrypt($symb);
-    $result.=<<ENDUPFORM;
-<script type="text/javascript">
+    $result.= &Apache::lonhtmlcommon::scripttag(<<ENDUPFORM);
 function sanitycheck() {
 // Accept only integer percentages
    document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
@@ -8719,56 +10191,59 @@ function sanitycheck() {
 // Remember the old state
    document.forms.gradesupload.waschecked.value=newgradingchoice;
 }
-</script>
+ENDUPFORM
+    $result.= <<ENDUPFORM;
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <input type="hidden" name="symb" value="$symb" />
 <input type="hidden" name="command" value="processclickerfile" />
-<input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
-<input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 <input type="file" name="upfile" size="50" />
 <br /><label>$type: $selectform</label>
-<br /><label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onClick="sanitycheck()" />$attendance </label>
-<br /><label><input type="radio" name="gradingmechanism" value="personnel"$checked{'personnel'} onClick="sanitycheck()" />$personnel</label>
-<br /><label><input type="radio" name="gradingmechanism" value="specific"$checked{'specific'} onClick="sanitycheck()" />$specific </label>
+ENDUPFORM
+    $result.='</td>'.&Apache::loncommon::end_data_table_row().
+                     &Apache::loncommon::start_data_table_row().'<td>'.(<<ENDGRADINGFORM);
+      <label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onclick="sanitycheck()" />$attendance </label>
+<br /><label><input type="radio" name="gradingmechanism" value="personnel"$checked{'personnel'} onclick="sanitycheck()" />$personnel</label>
+<br /><label><input type="radio" name="gradingmechanism" value="specific"$checked{'specific'} onclick="sanitycheck()" />$specific </label>
 <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
-<br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onClick="sanitycheck()" />$given </label>
+<br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onclick="sanitycheck()" />$given </label>
 <br />&nbsp;&nbsp;&nbsp;
 <input type="text" name="givenanswer" size="50" />
 <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
-<br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>
-<br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>
-<br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
-</form>
-ENDUPFORM
-    $result.='</td></tr></table>'."\n".
-             '</td></tr></table><br /><br />'."\n";
-    $result.=&show_grading_menu_form($symb);
+ENDGRADINGFORM
+         $result.='</td>'.&Apache::loncommon::end_data_table_row().
+                     &Apache::loncommon::start_data_table_row().'<td>'.(<<ENDPERCFORM);
+      <label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onchange="sanitycheck()" /></label>
+<br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onchange="sanitycheck()" /></label>
+<br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
+</form>'
+ENDPERCFORM
+    $result.='</td>'.
+             &Apache::loncommon::end_data_table_row().
+             &Apache::loncommon::end_data_table();
     return $result;
 }
 
 sub process_clicker_file {
-    my ($r)=@_;
-    my ($symb)=&get_symb($r);
+    my ($r,$symb)=@_;
     if (!$symb) {return '';}
 
     my %Saveable_Parameters=&clicker_grading_parameters();
     &Apache::loncommon::store_course_settings('grades_clicker',
                                               \%Saveable_Parameters);
-
-    my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
+    my $result='';
     if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
 	$result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
-	return $result.&show_grading_menu_form($symb);
+	return $result;
     }
     if (($env{'form.gradingmechanism'} eq 'given') && ($env{'form.givenanswer'}!~/\S/)) {
         $result.='<span class="LC_error">'.&mt('You need to specify the correct answer').'</span>';
-        return $result.&show_grading_menu_form($symb);
+        return $result;
     }
     my $foundgiven=0;
     if ($env{'form.gradingmechanism'} eq 'given') {
         $env{'form.givenanswer'}=~s/^\s*//gs;
         $env{'form.givenanswer'}=~s/\s*$//gs;
-        $env{'form.givenanswer'}=~s/[^a-zA-Z0-9\.\*\-]+/\,/g;
+        $env{'form.givenanswer'}=~s/[^a-zA-Z0-9\.\*\-\+]+/\,/g;
         $env{'form.givenanswer'}=uc($env{'form.givenanswer'});
         my @answers=split(/\,/,$env{'form.givenanswer'});
         $foundgiven=$#answers+1;
@@ -8807,33 +10282,34 @@ sub process_clicker_file {
 	    $number++;
 	}
         $result.="</p>\n";
-	if ($number==0) {
-	    $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
-	    return $result.&show_grading_menu_form($symb);
-	}
+        if ($number==0) {
+            $result .=
+                 &Apache::lonhtmlcommon::confirm_success(
+                     &mt('No IDs found to determine correct answer'),1);
+            return $result;
+        }
     }
     if (length($env{'form.upfile'}) < 2) {
-        $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.',
-		     '<span class="LC_error">',
-		     '</span>',
-		     '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');
-        return $result.&show_grading_menu_form($symb);
+        $result .=
+            &Apache::lonhtmlcommon::confirm_success(
+                &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
+                        '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1);
+        return $result;
     }
 
 # Were able to get all the info needed, now analyze the file
 
     $result.=&Apache::loncommon::studentbrowser_javascript();
     $symb = &Apache::lonenc::check_encrypt($symb);
-    my $heading=&mt('Scanning clicker file');
-    $result.=(<<ENDHEADER);
-<br /><table width="100%" border="0"><tr><td bgcolor="#777777">
-<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
-<b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
+    $result.=&Apache::loncommon::start_data_table().
+             &Apache::loncommon::start_data_table_header_row().
+             '<th>'.&mt('Evaluate clicker file').'</th>'.
+             &Apache::loncommon::end_data_table_header_row().
+             &Apache::loncommon::start_data_table_row().(<<ENDHEADER);
+<td>
 <form method="post" action="/adm/grades" name="clickeranalysis">
 <input type="hidden" name="symb" value="$symb" />
 <input type="hidden" name="command" value="assignclickergrades" />
-<input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
-<input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
 <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
 <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
@@ -8851,6 +10327,9 @@ ENDHEADER
     if ($env{'form.upfiletype'} eq 'interwrite') {
         ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
     }
+    if ($env{'form.upfiletype'} eq 'turning') {
+        ($errormsg,$number)=&turning_eval(\@questiontitles,\%responses);
+    }
     $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
              '<input type="hidden" name="number" value="'.$number.'" />'.
              &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
@@ -8858,7 +10337,7 @@ ENDHEADER
              '<br />';
     if (($env{'form.gradingmechanism'} eq 'given') && ($number!=$foundgiven)) {
        $result.='<span class="LC_error">'.&mt('Number of given answers does not agree with number of questions in file.').'</span>';
-       return $result.&show_grading_menu_form($symb);
+       return $result;
     } 
 # Remember Question Titles
 # FIXME: Possibly need delimiter other than ":"
@@ -8878,7 +10357,9 @@ ENDHEADER
        } elsif ($clicker_ids{$id}) {
           if ($clicker_ids{$id}=~/\,/) {
 # More than one user with the same clicker!
-             $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
+             $result.="</td>".&Apache::loncommon::end_data_table_row().
+                           &Apache::loncommon::start_data_table_row()."<td>".
+                       &mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
              $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                            "<select name='multi".$id."'>";
              foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
@@ -8892,12 +10373,14 @@ ENDHEADER
              $student_count++;
           }
        } else {
-          $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
+          $result.="</td>".&Apache::loncommon::end_data_table_row().
+                           &Apache::loncommon::start_data_table_row()."<td>".
+                    &mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
           $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                    "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
                    "\n".&mt("Domain").": ".
                    &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
-                   &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);
+                   &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id,0,$id);
           $unknown_count++;
        }
     }
@@ -8905,7 +10388,7 @@ ENDHEADER
              &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
     if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
        if ($correct_count==0) {
-          $errormsg.="Found no correct answers answers for grading!";
+          $errormsg.="Found no correct answers for grading!";
        } elsif ($correct_count>1) {
           $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
        }
@@ -8918,9 +10401,10 @@ ENDHEADER
     } else {
        $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
     }
-    $result.='</form></td></tr></table>'."\n".
-             '</td></tr></table><br /><br />'."\n";
-    return $result.&show_grading_menu_form($symb);
+    $result.='</form></td>'.
+             &Apache::loncommon::end_data_table_row().
+             &Apache::loncommon::end_data_table();
+    return $result;
 }
 
 sub iclicker_eval {
@@ -8942,6 +10426,7 @@ sub iclicker_eval {
 	    $id=~s/^[\#0]+//;
 	    for (my $i=0;$i<$number;$i++) {
 		my $idx=3+$i*6;
+                $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+]+//g;
 		push(@idresponses,$entries[$idx]);
 	    }
 	    $$responses{$id}=join(',',@idresponses);
@@ -8981,23 +10466,49 @@ sub interwrite_eval {
     return ($errormsg,$number);
 }
 
+sub turning_eval {
+    my ($questiontitles,$responses)=@_;
+    my $number=0;
+    my $errormsg='';
+    foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
+        my %components=&Apache::loncommon::record_sep($line);
+        my @entries=map {$components{$_}} (sort(keys(%components)));
+        if ($#entries>$number) { $number=$#entries; }
+        my $id=$entries[0];
+        my @idresponses;
+        $id=~s/^[\#0]+//;
+        unless ($id) { next; }
+        for (my $idx=1;$idx<=$#entries;$idx++) {
+            $entries[$idx]=~s/\,/\;/g;
+            $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+\;]+//g;
+            push(@idresponses,$entries[$idx]);
+        }
+        $$responses{$id}=join(',',@idresponses);
+    }
+    for (my $i=1; $i<=$number; $i++) {
+        $$questiontitles[$i]=&mt('Question [_1]',$i);
+    }
+    return ($errormsg,$number);
+}
+
+
 sub assign_clicker_grades {
-    my ($r)=@_;
-    my ($symb)=&get_symb($r);
+    my ($r,$symb)=@_;
     if (!$symb) {return '';}
 # See which part we are saving to
-    my ($partlist,$handgrade,$responseType) = &response_type($symb);
+    my $res_error;
+    my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
+    if ($res_error) {
+        return &navmap_errormsg();
+    }
 # FIXME: This should probably look for the first handgradeable part
     my $part=$$partlist[0];
 # Start screen output
-    my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
-
-    my $heading=&mt('Assigning grades based on clicker file');
-    $result.=(<<ENDHEADER);
-<br /><table width="100%" border="0"><tr><td bgcolor="#777777">
-<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
-<b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
-ENDHEADER
+    my $result=&Apache::loncommon::start_data_table().
+             &Apache::loncommon::start_data_table_header_row().
+             '<th>'.&mt('Assigning grades based on clicker file').'</th>'.
+             &Apache::loncommon::end_data_table_header_row().
+             &Apache::loncommon::start_data_table_row().'<td>';
 # Get correct result
 # FIXME: Possibly need delimiter other than ":"
     my @correct=();
@@ -9013,25 +10524,26 @@ ENDHEADER
                     $result.='<br /><span class="LC_warning">'.
                              &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
                                  $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
-                 } elsif ($input[$i]) {
+                 } elsif (($input[$i]) || ($input[$i] eq '0')) {
                     $correct[$i]=$input[$i];
                  }
              }
           }
        }
        for (my $i=0;$i<$number;$i++) {
-          if (!$correct[$i]) {
+          if ((!$correct[$i]) && ($correct[$i] ne '0')) {
              $result.='<br /><span class="LC_error">'.
                       &mt('No correct result given for question "[_1]"!',
                           $env{'form.question:'.$i}).'</span>';
           }
        }
-       $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));
+       $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ((($_) || ($_ eq '0'))?$_:'-') } @correct));
     }
 # Start grading
     my $pcorrect=$env{'form.pcorrect'};
     my $pincorrect=$env{'form.pincorrect'};
     my $storecount=0;
+    my %users=();
     foreach my $key (keys(%env)) {
        my $user='';
        if ($key=~/^form\.student\:(.*)$/) {
@@ -9045,24 +10557,42 @@ ENDHEADER
              $user=$env{'form.multi'.$id};
           }
        }
-       if ($user) { 
+       if ($user) {
+          if ($users{$user}) {
+             $result.='<br /><span class="LC_warning">'.
+                      &mt('More than one entry found for [_1]!','<tt>'.$user.'</tt>').
+                      '</span><br />';
+          }
+          $users{$user}=1; 
           my @answer=split(/\,/,$env{$key});
           my $sum=0;
           my $realnumber=$number;
           for (my $i=0;$i<$number;$i++) {
-             if ($answer[$i]) {
+             if  ($correct[$i] eq '-') {
+                $realnumber--;
+             } elsif (($answer[$i]) || ($answer[$i]=~/^[0\.]+$/))  {
                 if ($gradingmechanism eq 'attendance') {
                    $sum+=$pcorrect;
-                } elsif ($answer[$i] eq '*') {
+                } elsif ($correct[$i] eq '*') {
                    $sum+=$pcorrect;
-                } elsif ($answer[$i] eq '-') {
-                   $realnumber--;
                 } else {
-                   if ($answer[$i] eq $correct[$i]) {
-                      $sum+=$pcorrect;
-                   } else {
-                      $sum+=$pincorrect;
+# We actually grade if correct or not
+                   my $increment=$pincorrect;
+# Special case: numerical answer "0"
+                   if ($correct[$i] eq '0') {
+                      if ($answer[$i]=~/^[0\.]+$/) {
+                         $increment=$pcorrect;
+                      }
+# General numerical answer, both evaluate to something non-zero
+                   } elsif ((1.0*$correct[$i]!=0) && (1.0*$answer[$i]!=0)) {
+                      if (1.0*$correct[$i]==1.0*$answer[$i]) {
+                         $increment=$pcorrect;
+                      }
+# Must be just alphanumeric
+                   } elsif ($answer[$i] eq $correct[$i]) {
+                      $increment=$pcorrect;
                    }
+                   $sum+=$increment;
                 }
              }
           }
@@ -9085,140 +10615,271 @@ ENDHEADER
     }
 # We are done
     $result.='<br />'.&mt('Successfully stored grades for [quant,_1,student].',$storecount).
-             '</td></tr></table>'."\n".
-             '</td></tr></table><br /><br />'."\n";
-    return $result.&show_grading_menu_form($symb);
+             '</td>'.
+             &Apache::loncommon::end_data_table_row().
+             &Apache::loncommon::end_data_table();
+    return $result;
+}
+
+sub navmap_errormsg {
+    return '<div class="LC_error">'.
+           &mt('An error occurred retrieving information about resources in the course.').'<br />'.
+           &mt('It is recommended that you [_1]re-initialize the course[_2] and then return to this grading page.','<a href="/adm/roles?selectrole=1&newrole='.$env{'request.role'}.'">','</a>').
+           '</div>';
+}
+
+sub startpage {
+    my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js,$onload) = @_;
+    my %args;
+    if ($onload) {
+         my %loaditems = (
+                        'onload' => $onload,
+                      );
+         $args{'add_entries'} = \%loaditems;
+    }
+    if ($nomenu) {
+        $args{'only_body'} = 1; 
+        $r->print(&Apache::loncommon::start_page("Student's Version",$js,\%args);
+    } else {
+        unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
+        $args{'bread_crumbs'} = $crumbs;
+        $r->print(&Apache::loncommon::start_page('Grading',$js,\%args));
+        &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading'));
+    }
+    unless ($nodisplayflag) {
+       $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp));
+    }
+}
+
+sub select_problem {
+    my ($r)=@_;
+    $r->print('<h3>'.&mt('Select the problem or one of the problems you want to grade').'</h3><form action="/adm/grades">');
+    $r->print(&Apache::lonstathelpers::problem_selector('.',undef,1,undef,undef,undef,undef,1));
+    $r->print('<input type="hidden" name="command" value="gradingmenu" />');
+    $r->print('<input type="submit" value="'.&mt('Next').' &rarr;" /></form>');
 }
 
 sub handler {
     my $request=$_[0];
     &reset_caches();
-    if ($env{'browser.mathml'}) {
-	&Apache::loncommon::content_type($request,'text/xml');
-    } else {
-	&Apache::loncommon::content_type($request,'text/html');
+    if ($request->header_only) {
+        &Apache::loncommon::content_type($request,'text/html');
+        $request->send_http_header;
+        return OK;
     }
-    $request->send_http_header;
-    return '' if $request->header_only;
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
-    my $symb=&get_symb($request,1);
+
+# see what command we need to execute
+
     my @commands=&Apache::loncommon::get_env_multiple('form.command');
     my $command=$commands[0];
 
+    &init_perm();
+    if (!$env{'request.course.id'}) {
+        unless ((&Apache::lonnet::allowed('usc',$env{'request.role.domain'})) &&
+                ($command =~ /^scantronupload/)) {
+            # Not in a course.
+            $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context";
+            return HTTP_NOT_ACCEPTABLE;
+        }
+    } elsif (!%perm) {
+        $request->internal_redirect('/adm/quickgrades');
+        return OK;
+    }
+    &Apache::loncommon::content_type($request,'text/html');
+    $request->send_http_header;
+
     if ($#commands > 0) {
 	&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
     }
 
+# see what the symb is
+
+    my $symb=$env{'form.symb'};
+    unless ($symb) {
+       (my $url=$env{'form.url'}) =~ s-^https*://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
+       $symb=&Apache::lonnet::symbread($url);
+    }
+    &Apache::lonenc::check_decrypt(\$symb);
+
     $ssi_error = 0;
-    my $brcrum = [{href=>"/adm/grades",text=>"Grading"}];
-    $request->print(&Apache::loncommon::start_page('Grading',undef,
-                                          {'bread_crumbs' => $brcrum}));
-    if ($symb eq '' && $command eq '') {
-	if ($env{'user.adv'}) {
-	    if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&
-		($env{'form.codethree'})) {
-		my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'.
-		    $env{'form.codethree'};
-		my ($tsymb,$tuname,$tudom,$tcrsid)=
-		    &Apache::lonnet::checkin($token);
-		if ($tsymb) {
-		    my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
-		    if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
-			$request->print(&ssi_with_retries('/res/'.$url, $ssi_retries,
-					  ('grade_username' => $tuname,
-					   'grade_domain' => $tudom,
-					   'grade_courseid' => $tcrsid,
-					   'grade_symb' => $tsymb)));
-		    } else {
-			$request->print('<h3>Not authorized: '.$token.'</h3>');
-		    }
-		} else {
-		    $request->print('<h3>Not a valid DocID: '.$token.'</h3>');
-		}
-	    } else {
-		$request->print(&Apache::lonxml::tokeninputfield());
-	    }
-	}
+    if (($symb eq '' || $command eq '') && ($env{'request.course.id'})) {
+#
+# Not called from a resource, but inside a course
+#    
+        &startpage($request,undef,[],1,1);
+        &select_problem($request);
     } else {
-	&init_perm();
 	if ($command eq 'submission' && $perm{'vgr'}) {
-	    ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
+            my ($stuvcurrent,$stuvdisp,$versionform,$js);
+            if (($env{'form.student'} ne '') && ($env{'form.userdom'} ne '')) {
+                ($stuvcurrent,$stuvdisp,$versionform,$js) =
+                    &choose_task_version_form($symb,$env{'form.student'},
+                                              $env{'form.userdom'});
+            }
+            &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}],undef,undef,$stuvcurrent,$stuvdisp,undef,$js);
+            if ($versionform) {
+                $request->print($versionform);
+            }
+            $request->print('<br clear="all" />');
+	    ($env{'form.student'} eq '' ? &listStudents($request,$symb) : &submission($request,0,0,$symb));
+        } elsif ($command eq 'versionsub' && $perm{'vgr'}) {
+            my ($stuvcurrent,$stuvdisp,$versionform,$js) =
+                &choose_task_version_form($symb,$env{'form.student'},
+                                          $env{'form.userdom'},
+                                          $env{'form.inhibitmenu'});
+            &startpage($request,$symb,[{href=>"", text=>"Previous Student Version"}],undef,undef,$stuvcurrent,$stuvdisp,$env{'form.inhibitmenu'},$js);
+            if ($versionform) {
+                $request->print($versionform);
+            }
+            $request->print('<br clear="all" />');
+            $request->print(&show_previous_task_version($request,$symb));
 	} elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
-	    &pickStudentPage($request);
+            &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
+                                       {href=>'',text=>'Select student'}],1,1);
+	    &pickStudentPage($request,$symb);
 	} elsif ($command eq 'displayPage' && $perm{'vgr'}) {
-	    &displayPage($request);
+            &startpage($request,$symb,
+                                      [{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
+                                       {href=>'',text=>'Select student'},
+                                       {href=>'',text=>'Grade student'}],1,1);
+	    &displayPage($request,$symb);
 	} elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
-	    &updateGradeByPage($request);
+            &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
+                                       {href=>'',text=>'Select student'},
+                                       {href=>'',text=>'Grade student'},
+                                       {href=>'',text=>'Store grades'}],1,1);
+	    &updateGradeByPage($request,$symb);
 	} elsif ($command eq 'processGroup' && $perm{'vgr'}) {
-	    &processGroup($request);
+            &startpage($request,$symb,[{href=>'',text=>'...'},
+                                       {href=>'',text=>'Modify grades'}]);
+	    &processGroup($request,$symb);
 	} elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
-	    $request->print(&grading_menu($request));
-	} elsif ($command eq 'submit_options' && $perm{'vgr'}) {
-	    $request->print(&submit_options($request));
+            &startpage($request,$symb);
+	    $request->print(&grading_menu($request,$symb));
+	} elsif ($command eq 'individual' && $perm{'vgr'}) {
+            &startpage($request,$symb,[{href=>'',text=>'Select individual students to grade'}]);
+	    $request->print(&submit_options($request,$symb));
+        } elsif ($command eq 'ungraded' && $perm{'vgr'}) {
+            &startpage($request,$symb,[{href=>'',text=>'Grade ungraded submissions'}]);
+            $request->print(&listStudents($request,$symb,'graded'));
+        } elsif ($command eq 'table' && $perm{'vgr'}) {
+            &startpage($request,$symb,[{href=>"", text=>"Grading table"}]);
+            $request->print(&submit_options_table($request,$symb));
+        } elsif ($command eq 'all_for_one' && $perm{'vgr'}) {
+            &startpage($request,$symb,[{href=>'',text=>'Grade page/folder for one student'}],1,1);
+            $request->print(&submit_options_sequence($request,$symb));
 	} elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
-	    $request->print(&viewgrades($request));
+            &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},{href=>'', text=>"Modify grades"}]);
+	    $request->print(&viewgrades($request,$symb));
 	} elsif ($command eq 'handgrade' && $perm{'mgr'}) {
-	    $request->print(&processHandGrade($request));
+            &startpage($request,$symb,[{href=>'',text=>'...'},
+                                       {href=>'',text=>'Store grades'}]);
+	    $request->print(&processHandGrade($request,$symb));
 	} elsif ($command eq 'editgrades' && $perm{'mgr'}) {
-	    $request->print(&editgrades($request));
+            &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},
+                                       {href=>&href_symb_cmd($symb,'viewgrades').'&group=all&section=all&Status=Active',
+                                                                             text=>"Modify grades"},
+                                       {href=>'', text=>"Store grades"}]);
+	    $request->print(&editgrades($request,$symb));
+        } elsif ($command eq 'initialverifyreceipt' && $perm{'vgr'}) {
+            &startpage($request,$symb,[{href=>'',text=>'Verify Receipt Number'}]);
+            $request->print(&initialverifyreceipt($request,$symb));
 	} elsif ($command eq 'verify' && $perm{'vgr'}) {
-	    $request->print(&verifyreceipt($request));
+            &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"initialverifyreceipt"),text=>'Verify Receipt Number'},
+                                       {href=>'',text=>'Verification Result'}]);
+	    $request->print(&verifyreceipt($request,$symb));
         } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
-            $request->print(&process_clicker($request));
+            &startpage($request,$symb,[{href=>'', text=>'Process clicker'}]);
+            $request->print(&process_clicker($request,$symb));
         } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
-            $request->print(&process_clicker_file($request));
+            &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
+                                       {href=>'', text=>'Process clicker file'}]);
+            $request->print(&process_clicker_file($request,$symb));
         } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
-            $request->print(&assign_clicker_grades($request));
+            &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
+                                       {href=>'', text=>'Process clicker file'},
+                                       {href=>'', text=>'Store grades'}]);
+            $request->print(&assign_clicker_grades($request,$symb));
 	} elsif ($command eq 'csvform' && $perm{'mgr'}) {
-	    $request->print(&upcsvScores_form($request));
+            &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+	    $request->print(&upcsvScores_form($request,$symb));
 	} elsif ($command eq 'csvupload' && $perm{'mgr'}) {
-	    $request->print(&csvupload($request));
+            &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+	    $request->print(&csvupload($request,$symb));
 	} elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
-	    $request->print(&csvuploadmap($request));
+            &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+	    $request->print(&csvuploadmap($request,$symb));
 	} elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
 	    if ($env{'form.associate'} ne 'Reverse Association') {
-		$request->print(&csvuploadoptions($request));
+                &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+		$request->print(&csvuploadoptions($request,$symb));
 	    } else {
 		if ( $env{'form.upfile_associate'} ne 'reverse' ) {
 		    $env{'form.upfile_associate'} = 'reverse';
 		} else {
 		    $env{'form.upfile_associate'} = 'forward';
 		}
-		$request->print(&csvuploadmap($request));
+                &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+		$request->print(&csvuploadmap($request,$symb));
 	    }
 	} elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
-	    $request->print(&csvuploadassign($request));
+            &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+	    $request->print(&csvuploadassign($request,$symb));
 	} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
-	    $request->print(&scantron_selectphase($request));
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1,
+                       undef,undef,undef,undef,'toggleScantab(document.rules);');
+	    $request->print(&scantron_selectphase($request,undef,$symb));
  	} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
- 	    $request->print(&scantron_do_warning($request));
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ 	    $request->print(&scantron_do_warning($request,$symb));
 	} elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
-	    $request->print(&scantron_validate_file($request));
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+	    $request->print(&scantron_validate_file($request,$symb));
 	} elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
-	    $request->print(&scantron_process_students($request));
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+	    $request->print(&scantron_process_students($request,$symb));
  	} elsif ($command eq 'scantronupload' && 
  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
- 	    $request->print(&scantron_upload_scantron_data($request)); 
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1,
+                       undef,undef,undef,undef,'toggleScantab(document.rules);');
+ 	    $request->print(&scantron_upload_scantron_data($request,$symb)); 
  	} elsif ($command eq 'scantronupload_save' &&
  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
- 	    $request->print(&scantron_upload_scantron_data_save($request));
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ 	    $request->print(&scantron_upload_scantron_data_save($request,$symb));
  	} elsif ($command eq 'scantron_download' &&
 		 &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
- 	    $request->print(&scantron_download_scantron_data($request));
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ 	    $request->print(&scantron_download_scantron_data($request,$symb));
         } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) {
-            $request->print(&checkscantron_results($request));     
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+            $request->print(&checkscantron_results($request,$symb));
+        } elsif ($command eq 'downloadfilesselect' && $perm{'vgr'}) {
+            &startpage($request,$symb,[{href=>'', text=>'Select which submissions to download'}]);
+            $request->print(&submit_options_download($request,$symb));
+         } elsif ($command eq 'downloadfileslink' && $perm{'vgr'}) {
+            &startpage($request,$symb,
+   [{href=>&href_symb_cmd($symb,'downloadfilesselect'), text=>'Select which submissions to download'},
+    {href=>'', text=>'Download submitted files'}]);
+            &submit_download_link($request,$symb);
 	} elsif ($command) {
+            &startpage($request,$symb,[{href=>'', text=>'Access denied'}]);
 	    $request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
 	}
     }
     if ($ssi_error) {
 	&ssi_print_error($request);
     }
-    $request->print(&Apache::loncommon::end_page());
+    if ($env{'form.inhibitmenu'}) {
+        $request->print(&Apache::loncommon::end_page());
+    } else {
+        &Apache::lonquickgrades::endGradeScreen($request);
+    }
     &reset_caches();
-    return '';
+    return OK;
 }
 
 1;
@@ -9240,7 +10901,7 @@ described at http://www.lon-capa.org.
 =head1 OVERVIEW
 
 Do an ssi with retries:
-While I'd love to factor out this with the vesrion in lonprintout,
+While I'd love to factor out this with the version 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).
 
@@ -9251,11 +10912,11 @@ At least the logic that drives this has
 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
+                     If we still have a problem, 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:
+                     in LON-CAPA BZ 5631 see:
                      http://bugs.lon-capa.org/show_bug.cgi?id=5631
                      by informing the user that this happened.
 
@@ -9287,6 +10948,75 @@ ssi_with_retries()
 
 =over
 
+=head1 Routines to display previous version of a Task for a specific student
+
+Tasks are graded pass/fail. Students who have yet to pass a particular Task
+can receive another opportunity. Access to tasks is slot-based. If a slot
+requires a proctor to check-in the student, a new version of the Task will
+be created when the student is checked in to the new opportunity.
+
+If a particular student has tried two or more versions of a particular task,
+the submission screen provides a user with vgr privileges (e.g., a Course
+Coordinator) the ability to display a previous version worked on by the
+student.  By default, the current version is displayed. If a previous version
+has been selected for display, submission data are only shown that pertain
+to that particular version, and the interface to submit grades is not shown.
+
+=over 4
+
+=item show_previous_task_version()
+
+Displays a specified version of a student's Task, as the student sees it.
+
+Inputs: 2
+        request - request object
+        symb    - unique symb for current instance of resource
+
+Output: None.
+
+Side Effects: calls &show_problem() to print version of Task, with
+              version contained in form item: $env{'form.previousversion'}
+
+=item choose_task_version_form()
+
+Displays a web form used to select which version of a student's view of a
+Task should be displayed.  Either launches a pop-up window, or replaces
+content in existing pop-up, or replaces page in main window.
+
+Inputs: 4
+        symb    - unique symb for current instance of resource
+        uname   - username of student
+        udom    - domain of student
+        nomenu  - 1 if display is in a pop-up window, and hence no menu
+                  breadcrumbs etc., are displayed
+
+Output: 4
+        current   - student's current version
+        displayed - student's version being displayed
+        result    - scalar containing HTML for web form used to switch to
+                    a different version (or a link to close window, if pop-up).
+        js        - javascript for processing selection in versions web form
+
+Side Effects: None.
+
+=item previous_display_javascript()
+
+Inputs: 2
+        nomenu  - 1 if display is in a pop-up window, and hence no menu
+                  breadcrumbs etc., are displayed.
+        current - student's current version number.
+
+Output: 1
+        js      - javascript for processing selection in versions web form.
+
+Side Effects: None.
+
+=back
+
+=head1 Routines to process bubblesheet data.
+
+=over 4
+
 =item scantron_get_correction() : 
 
    Builds the interface screen to interact with the operator to fix a
@@ -9313,8 +11043,28 @@ ssi_with_retries()
          - 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
+   $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).
+   $startline   - Reference to hash where key is question number (0 is first)
+                  and value is number of first bubble line for current student
+                  or code-based randompick and/or randomorder.
+
+
+
 =item  scantron_get_maxbubble() : 
 
+   Arguments:
+       $nav_error  - Reference to scalar which is a flag to indicate a
+                      failure to retrieve a navmap object.
+       if $nav_error is set to 1 by scantron_get_maxbubble(), the 
+       calling routine should trap the error condition and display the warning
+       found in &navmap_errormsg().
+
+       $scantron_config - Reference to bubblesheet format configuration hash.
+
    Returns the maximum number of bubble lines that are expected to
    occur. Does this by walking the selected sequence rendering the
    resource and then checking &Apache::lonxml::get_problem_counter()
@@ -9324,7 +11074,7 @@ ssi_with_retries()
    $env{'form.scantron.bubble_lines.n'}, 
    $env{'form.scantron.first_bubble_line.n'} and
    $env{"form.scantron.sub_bubblelines.n"}
-   which are the total number of bubble, lines, the number of bubble
+   which are the total number of bubble lines, the number of bubble
    lines for response n and number of the first bubble line for response n,
    and a comma separated list of numbers of bubble lines for sub-questions
    (for optionresponse, matchresponse, and rankresponse items), for response n.  
@@ -9338,7 +11088,7 @@ ssi_with_retries()
 
 =item  scantron_process_students() : 
 
-   Routine that does the actual grading of the bubble sheet information.
+   Routine that does the actual grading of the bubblesheet information.
 
    The parsed scanline hash is added to %env 
 
@@ -9358,7 +11108,7 @@ ssi_with_retries()
 
 =item  scantron_upload_scantron_data() :
 
-    Creates the screen for adding a new bubble sheet data file to a course.
+    Creates the screen for adding a new bubblesheet data file to a course.
 
 =item  scantron_upload_scantron_data_save() : 
 
@@ -9372,7 +11122,7 @@ ssi_with_retries()
 =item  scantron_download_scantron_data() : 
 
    Shows a list of the three internal files (original, corrected,
-   skipped) for a specific bubble sheet data file that exists in the
+   skipped) for a specific bubblesheet data file that exists in the
    course.
 
 =item  scantron_validate_ID() : 
@@ -9380,6 +11130,13 @@ ssi_with_retries()
    Validates all scanlines in the selected file to not have any
    invalid or underspecified student/employee IDs
 
+=item navmap_errormsg() :
+
+   Returns HTML mark-up inside a <div></div> with a link to re-initialize the course.
+   Should be called whenever the request to instantiate a navmap object fails.
+
+=back
+
 =back
 
 =cut