--- loncom/homework/grades.pm	2011/09/13 21:42:58	1.649
+++ loncom/homework/grades.pm	2012/12/10 01:13:08	1.677
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.649 2011/09/13 21:42:58 raeburn Exp $
+# $Id: grades.pm,v 1.677 2012/12/10 01:13:08 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -45,6 +45,7 @@ use Apache::lonlocal;
 use Apache::lonenc;
 use Apache::lonstathelpers;
 use Apache::lonquickgrades;
+use Apache::bridgetask();
 use String::Similarity;
 use LONCAPA;
 
@@ -53,6 +54,7 @@ use POSIX qw(floor);
 
 
 my %perm=();
+my %old_essays=();
 
 #  These variables are used to recover from ssi errors
 
@@ -201,6 +203,7 @@ sub get_display_part {
 sub reset_caches {
     &reset_analyze_cache();
     &reset_perm();
+    &reset_old_essays();
 }
 
 {
@@ -340,7 +343,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 'match') {
 	my %answer=&Apache::lonnet::str2hash($answer);
 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
@@ -362,7 +365,7 @@ 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 ($toprow,$bottomrow);
@@ -383,7 +386,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>'.
-	    $bottomrow.'</tr>'.'</table></blockquote>';
+	    $bottomrow.'</tr></table></blockquote>';
     } elsif ($response eq 'essay') {
 	if (! exists ($env{'form.'.$symb})) {
 	    my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
@@ -680,7 +683,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
 
@@ -697,11 +704,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);
@@ -711,7 +718,7 @@ sub most_similar {
 	    $sname=$tname;
 	    $sdom=$tdom;
 	    $scrsid=$tcrsid;
-	    $sessay=$old_essays->{$tkey};
+	    $sessay=$old_essays{$symb}{$tkey};
 	}
     }
     if ($limit>0.6) {
@@ -1408,12 +1415,28 @@ 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.');
+    my %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.',
+                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',
+             );
     $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("$lt{'keyw'}",formname.keywords.value);
     if (nret==null) return;
     formname.keywords.value = nret;
 
@@ -1440,10 +1463,10 @@ INNERJS
     else return;
     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
     if (cleantxt=="") {
-	alert("$alertmsg");
+	alert("$lt{'plse'}");
 	return;
     }
-    var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
+    var nret = prompt("$lt{'adds'}",cleantxt);
     if (nret==null) return;
     document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
     if (document.SCORE.keywords.value != "") {
@@ -1507,17 +1530,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=700,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;
@@ -1525,42 +1546,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;$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>$lt{'incl'}<\\/b><\\/td><td><b>$lt{'type'}<\\/b><\\/td><td><b>$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>$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\\">$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=\\"$lt{'save'}\\" onclick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
+    pDoc.write("<input type=\\"button\\" value=\\"$lt{'canc'}\\" onclick=\\"self.close()\\"><br /><br />");
     pDoc.write("<\\/form>");
     pDoc.write('$end_page_msg_central');
     pDoc.close();
@@ -1610,11 +1630,11 @@ 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("<h3><span class=\\"LC_info\\">&nbsp;$lt{'kehi'}<\\/span><\\/h3><br /><br />");
 
     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("<td><b>$lt{'txtc'}<\\/b><\\/td><td><b>$lt{'font'}<\\/b><\\/td><td><b>$lt{'fnst'}<\\/td><\\/tr>");
   }
 
   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
@@ -1633,8 +1653,8 @@ INNERJS
     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("<input type=\\"button\\" value=\\"$lt{'save'}\\" onclick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");
+    hDoc.write("<input type=\\"button\\" value=\\"$lt{'canc'}\\" onclick=\\"self.close()\\"><br /><br />");
     hDoc.write("<\\/form>");
     hDoc.write('$end_page_highlight_central');
     hDoc.close();
@@ -1749,7 +1769,7 @@ sub handback_box {
     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) {
@@ -1761,9 +1781,10 @@ 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);
     	        $file_disp = "$name.$ext";
@@ -1771,11 +1792,14 @@ sub handback_box {
     	        $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;    
 }
@@ -1808,14 +1832,27 @@ 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_Box">'
-       .'<h3 class="LC_hcell">'.&mt('View of the problem').'</h3>'
+       .'<h3 class="LC_hcell">'.$renderheading.'</h3>'
        .$rendered
        .'</div>';
     $companswer=
         '<div class="LC_Box">'
-       .'<h3 class="LC_hcell">'.&mt('Correct answer').'</h3>'
+       .'<h3 class="LC_hcell">'.$answerheading.'</h3>'
        .$companswer
        .'</div>';
     my $result;
@@ -1917,7 +1954,6 @@ sub submission {
 	'" src="'.$request->dir_config('lonIconsURL').
 	'/check.gif" height="16" border="0" />';
 
-    my %old_essays;
     # header info
     if ($counter == 0) {
 	&sub_page_js($request);
@@ -2001,15 +2037,22 @@ sub submission {
 
 #	if ($env{'form.handgrade'} eq 'yes') {
         if (1) {
+
+            my %lt = &Apache::lonlocal::texthash(
+                          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;
+<br /><b>$lt{'keyw'}:</b>&nbsp;
+<a href="javascript:keywords(document.SCORE);" target="_self">$lt{'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 />
+ CLASS="page">$lt{'past'}</a>&nbsp; &nbsp;
+<a href="javascript:kwhighlight();" target="_self">$lt{'high'}</a><br /><br />
 KEYWORDS
 #
 # Load the other essays for similarity check
@@ -2018,7 +2061,7 @@ KEYWORDS
 	    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);
+            &init_old_essays($symb,$apath,$adom,$aname);
         }
     }
 
@@ -2157,7 +2200,7 @@ KEYWORDS
                     }
 		    if($env{'form.checkPlag'}){
 			my ($oname,$odom,$ocrsid,$oessay,$osim)=
-			    &most_similar($uname,$udom,$subval,\%old_essays);
+			    &most_similar($uname,$udom,$symb,$subval);
 			if ($osim) {
 			    $osim=int($osim*100.0);
 			    my %old_course_desc = 
@@ -2480,6 +2523,183 @@ 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("Unable to handle ambiguous references.");
+
+        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">Unable to view previous version for requested student.('.
+                        $uname.':'.$udom.' in section '.$usec.' in course id '.
+                        $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,$symb) = @_;
@@ -2531,7 +2751,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");
@@ -2709,7 +2929,7 @@ sub processHandGrade {
 	$ctr++;
     }
     if ($total < 0) {
-	my $the_end.=&mt('<b>Message: </b> No more students for this section or class.').'<br /><br />'."\n";
+	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 '';
@@ -2860,64 +3080,79 @@ sub handback_files {
         $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);
 		    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);
+                    my ($dir_list,$listerror) = 
+                        &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,
+                                                 $domain,$stuname,$getpropath);
+		    my $version = &get_next_version($answer_name,$answer_ext,$dir_list);
                     # fix file name
                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
                     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++;
+                    $request->print('<br />'.&mt('[_1] will be the uploaded file name [_2]','<span class="LC_info">'.$fname.'</span>','<span class="LC_filename">'.$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter}.'</span>'));
                 }
-		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);
             }
         }
+    }
+    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;
 }
 
@@ -3026,8 +3261,10 @@ sub version_portfiles {
 		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 ($dir_list,$listerror) = 
+                    &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);
@@ -3046,21 +3283,24 @@ sub version_portfiles {
 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 (ref($dir_list) eq 'ARRAY') {
+        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;
-	        }
+                    # a versioned file is found  so save it for later
+                    if ($file_version > $version) {
+		        $version = $file_version;
+	            }
+                }
             }
         }
-    } 
+    }
     $version ++;
     return($version);
 }
@@ -4049,6 +4289,7 @@ sub csvuploadassign {
     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);
@@ -4098,7 +4339,7 @@ sub csvuploadassign {
                     my $award=($pcr == 0) ? 'incorrect_by_override'
                                           : 'correct_by_override';
                     if ($pcr>1) {
-                       push(@skipped,&mt("[_1]: point value larger than weight","$username:$domain"));
+                       push(@warnings,&mt("[_1]: point value larger than weight","$username:$domain"));
                     }
                     $grades{"resource.$part.awarded"}=$pcr;
                     $grades{"resource.$part.solved"}=$award;
@@ -4144,6 +4385,10 @@ sub csvuploadassign {
         }
     }
     $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));
@@ -4487,6 +4732,7 @@ sub displaySubByDates {
 	&Apache::loncommon::start_data_table_header_row().
 	'<th>'.&mt('Date/Time').'</th>'.
 	($isCODE?'<th>'.&mt('CODE').'</th>':'').
+        ($isTask?'<th>'.&mt('Version').'</th>':'').
 	'<th>'.&mt('Submission').'</th>'.
 	'<th>'.&mt('Status').'</th>'.
 	&Apache::loncommon::end_data_table_header_row();
@@ -4507,7 +4753,9 @@ sub displaySubByDates {
 	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().
@@ -4515,6 +4763,9 @@ 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}) {
@@ -4534,7 +4785,7 @@ sub displaySubByDates {
                     
 		    my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
 				               : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
-                    $displaySub[0].='<span class="LC_nobreak"';
+                    $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).')'
@@ -4791,12 +5042,12 @@ 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.
 
@@ -4812,14 +5063,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
@@ -4953,14 +5204,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;
 }
@@ -5114,7 +5367,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)
@@ -5357,6 +5610,7 @@ sub scantron_selectphase {
       LastNameLength - number of columns that the last name spans
       BubblesPerRow - number of bubbles available in each row used to 
                       bubble an answer. (If not specified, 10 assumed).
+
 =cut
 
 sub get_scantron_config {
@@ -6136,7 +6390,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
@@ -6158,7 +6412,7 @@ 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 $CODElist;
@@ -6171,6 +6425,12 @@ sub scantron_warning_screen {
 	    '<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">
@@ -6179,11 +6439,10 @@ sub scantron_warning_screen {
 <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 />
 ');
@@ -6217,9 +6476,10 @@ sub scantron_do_warning {
 	    $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" />
 ');
@@ -6271,7 +6531,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,
@@ -6315,6 +6575,9 @@ sub scantron_validate_file {
         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',
@@ -6339,7 +6602,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?').
@@ -6349,7 +6612,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 {
@@ -6361,7 +6624,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)" />');
@@ -6382,7 +6645,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
 
 
@@ -6407,7 +6670,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.)
@@ -6444,7 +6707,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:
@@ -6544,7 +6807,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.
 
@@ -6638,7 +6901,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:
@@ -6740,7 +7003,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'
+                   .' exam mode. Grading these resources currently may not'
+                   .' work correctly.')
+               .'</p>'
+            );
 	    return (1,$currentphase);
 	}
     }
@@ -6827,19 +7096,28 @@ sub scantron_get_correction {
 #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");
@@ -6849,10 +7127,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");
@@ -6868,14 +7146,15 @@ sub scantron_get_correction {
 	$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' 
@@ -6902,7 +7181,7 @@ 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 />");
@@ -6940,7 +7219,7 @@ 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:
@@ -6958,7 +7237,7 @@ ENDSCRIPT
 	}
         $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");
@@ -7444,7 +7723,8 @@ sub scantron_get_maxbubble {
     my $response_number = 0;
     my $bubble_line     = 0;
     foreach my $resource (@resources) {
-        my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom,undef,$bubbles_per_row);
+        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;
@@ -7577,6 +7857,41 @@ 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=&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"/>0 points</label></p>';
+    }
+    return;
+}
 
 sub scantron_process_students {
     my ($r,$symb) = @_;
@@ -7599,22 +7914,29 @@ sub scantron_process_students {
         return '';
     }  
     my $map=$navmap->getResourceByUrl($sequence);
+    my $randomorder;
+    if (ref($map)) {
+        $randomorder = $map->randomorder();
+    }
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
-    my (%grader_partids_by_symb,%grader_randomlists_by_symb);
+    my (%grader_partids_by_symb,%grader_randomlists_by_symb,%ordered);
     &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                             \%grader_randomlists_by_symb,$bubbles_per_row);
-    my $resource_error;
+    my ($resource_error,%symb_to_resource,@master_seq);
     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;
         }
         my ($analysis,$parts) =
             &scantron_partids_tograde($resource,$env{'request.course.id'},
-                                      $env{'user.name'},$env{'user.domain'},1,$bubbles_per_row);
+                                      $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') {
@@ -7641,11 +7963,8 @@ 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;
@@ -7677,8 +7996,7 @@ 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 $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
@@ -7694,10 +8012,38 @@ 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 $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 && $scancode) {
+            unless (ref($ordered{$scancode}) eq 'ARRAY') {
+                $env{'form.CODE'} = $scancode;
+                my $actual_seq =
+                    &Apache::lonprintout::master_seq_to_person_seq($sequence,
+                                                                   \@master_seq,
+                                                                   $user,$scancode);
+                if (ref($actual_seq) eq 'ARRAY') {
+                    @{$ordered{$scancode}} = 
+                        map { $symb_to_resource{$_}; } @{$actual_seq};
+                }
+                delete($env{'form.CODE'});
+            }
+            if (ref($ordered{$scancode}) eq 'ARRAY') {
+                @mapresources = @{$ordered{$scancode}};
+            }
+        }
         my (%partids_by_symb,$res_error);
-        foreach my $resource (@resources) {
+        foreach my $resource (@mapresources) {
             my $ressymb;
             if (ref($resource)) {
                 $ressymb = $resource->symb();
@@ -7708,7 +8054,8 @@ SCANTRONFORM
             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
                 my ($analysis,$parts) =
-                    &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom,undef,$bubbles_per_row);
+                    &scantron_partids_tograde($resource,$env{'request.course.id'},
+                                              $uname,$udom,undef,$bubbles_per_row);
                 $partids_by_symb{$ressymb} = $parts;
             } else {
                 $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
@@ -7728,16 +8075,8 @@ 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,
+                                   \@mapresources,\%partids_by_symb,
                                    $bubbles_per_row) eq 'ssi_error') {
             $ssi_error = 0; # So end of handler error message does not trigger.
             $r->print("</form>");
@@ -7754,7 +8093,7 @@ SCANTRONFORM
             $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'},
@@ -7765,7 +8104,7 @@ SCANTRONFORM
             if ($studentrecord ne $studentdata) {
                 &Apache::lonxml::clear_problem_counter();
                 if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
-                                           \@resources,\%partids_by_symb,
+                                           \@mapresources,\%partids_by_symb,
                                            $bubbles_per_row) eq 'ssi_error') {
                     $ssi_error = 0; # So end of handler error message does not trigger.
                     $r->print("</form>");
@@ -7776,7 +8115,7 @@ SCANTRONFORM
                 }
                 $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'},
@@ -7785,12 +8124,12 @@ SCANTRONFORM
                     $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".
@@ -7798,12 +8137,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"><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"><tt>'.$studentrecord.'</tt></span></td>'."\n".
                               &Apache::loncommon::end_data_table_row().
                               &Apache::loncommon::end_data_table().'</p>');
                 } else {
@@ -7837,7 +8176,8 @@ sub graders_resources_pass {
             my $ressymb = $resource->symb();
             my ($analysis,$parts) =
                 &scantron_partids_tograde($resource,$env{'request.course.id'},
-                                          $env{'user.name'},$env{'user.domain'},1,$bubbles_per_row);
+                                          $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') {
@@ -7851,8 +8191,7 @@ sub graders_resources_pass {
 }
 
 sub grade_student_bubbles {
-    my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row) = @_;
-# Walk folder as student here to get resources in order student sees.
+    my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row) = @_; 
     if (ref($resources) eq 'ARRAY') {
         my $count = 0;
         foreach my $resource (@{$resources}) {
@@ -7868,6 +8207,9 @@ sub grade_student_bubbles {
             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}}) {
@@ -8153,10 +8495,14 @@ sub checkscantron_results {
         return '';
     }
     my $map=$navmap->getResourceByUrl($sequence);
+    my $randomorder;
+    if (ref($map)) { 
+        $randomorder=$map->randomorder();
+    }
     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);
-
+    &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+                            \%grader_randomlists_by_symb,$bubbles_per_row);
     my ($uname,$udom);
     my (%scandata,%lastname,%bylast);
     $r->print('
@@ -8166,9 +8512,7 @@ sub checkscantron_results {
     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 %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
     my ($username,$domain,$started);
     my $nav_error;
     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
@@ -8177,8 +8521,7 @@ sub checkscantron_results {
         return '';
     }
 
-    &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;
 
@@ -8188,8 +8531,7 @@ 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=
@@ -8214,14 +8556,39 @@ sub checkscantron_results {
         chomp($scandata{$pid});
         $scandata{$pid} =~ s/\r$//;
         ($username,$domain)=split(/:/,$uname);
+
+        my ($scancode,%ordered);
+        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 && $scancode) {
+            unless (ref($ordered{$scancode}) eq 'ARRAY') {
+                $env{'form.CODE'} = $scancode;
+                $ordered{$scancode} = 
+                    &Apache::lonprintout::master_seq_to_person_seq($sequence,
+                                                                   \@resources,
+                                                                   $uname,$scancode);
+                delete($env{'form.CODE'});
+            }
+            if (ref($ordered{$scancode}) eq 'ARRAY') {
+                @mapresources = @{$ordered{$scancode}};
+            }
+        }
         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 $analysis,$parts) =
-                    &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain,undef,$bubbles_per_row);
+                    &scantron_partids_tograde($resource,$env{'request.course.id'},
+                                              $username,$domain,undef,
+                                              $bubbles_per_row);
             } else {
                 $parts = $grader_partids_by_symb{$ressymb};
             }
@@ -8441,7 +8808,7 @@ sub letter_to_digits {
 
 sub href_symb_cmd {
     my ($symb,$cmd)=@_;
-    return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&command='.$cmd;
+    return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&amp;command='.$cmd;
 }
 
 sub grading_menu {
@@ -8704,6 +9071,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;
 
@@ -8801,7 +9183,8 @@ sub process_clicker {
     my $pincorrect=&mt("Percentage points for incorrect solution");
     my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
 						   {'iclicker' => 'i>clicker',
-                                                    'interwrite' => 'interwrite PRS'});
+                                                    'interwrite' => 'interwrite PRS',
+                                                    'turning' => 'Turning Technologies'});
     $symb = &Apache::lonenc::check_encrypt($symb);
     $result.= &Apache::lonhtmlcommon::scripttag(<<ENDUPFORM);
 function sanitycheck() {
@@ -8975,6 +9358,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',
@@ -9111,6 +9497,32 @@ 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,$symb)=@_;
     if (!$symb) {return '';}
@@ -9248,13 +9660,17 @@ sub navmap_errormsg {
 }
 
 sub startpage {
-    my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag) = @_;
-    unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
-    $r->print(&Apache::loncommon::start_page('Grading',undef,
-                                          {'bread_crumbs' => $crumbs}));
-    &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading'));
+    my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js) = @_;
+    if ($nomenu) {
+        $r->print(&Apache::loncommon::start_page("Student's Version",$js,{'only_body' => '1'}));
+    } else {
+        unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
+        $r->print(&Apache::loncommon::start_page('Grading',$js,
+                                                 {'bread_crumbs' => $crumbs}));
+        &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading'));
+    }
     unless ($nodisplayflag) {
-       $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag));
+       $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp));
     }
 }
 
@@ -9276,23 +9692,25 @@ sub handler {
     }
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
 
+# 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'}) {
-        # Not in a course.
-        $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context";
-        return HTTP_NOT_ACCEPTABLE;
+        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');
     }
     &Apache::loncommon::content_type($request,'text/html');
     $request->send_http_header;
 
-
-# see what command we need to execute
-
-    my @commands=&Apache::loncommon::get_env_multiple('form.command');
-    my $command=$commands[0];
-
     if ($#commands > 0) {
 	&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
     }
@@ -9315,8 +9733,29 @@ sub handler {
         &select_problem($request);
     } else {
 	if ($command eq 'submission' && $perm{'vgr'}) {
-            &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}]);
+            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'}) {
             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
                                        {href=>'',text=>'Select student'}],1,1);
@@ -9454,8 +9893,11 @@ sub handler {
     if ($ssi_error) {
 	&ssi_print_error($request);
     }
-    &Apache::lonquickgrades::endGradeScreen($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 OK;
 }
@@ -9526,6 +9968,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
@@ -9586,7 +10097,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 
 
@@ -9606,7 +10117,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() : 
 
@@ -9620,7 +10131,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() : 
@@ -9631,7 +10142,9 @@ ssi_with_retries()
 =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.  
+   Should be called whenever the request to instantiate a navmap object fails.
+
+=back
 
 =back