--- loncom/homework/grades.pm	2004/09/27 20:59:21	1.216
+++ loncom/homework/grades.pm	2005/02/17 08:58:16	1.247
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.216 2004/09/27 20:59:21 www Exp $
+# $Id: grades.pm,v 1.247 2005/02/17 08:58:16 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -25,7 +25,6 @@
 #
 # http://www.lon-capa.org/
 #
-###
 
 package Apache::grades;
 use strict;
@@ -92,25 +91,6 @@ sub get_symb_and_url {
     return ($symb,$url);
 }
 
-# --- Retrieve the fullname for a user. Return lastname, first middle ---
-# --- Generation is attached next to the lastname if it exists. ---
-sub get_fullname {
-    my ($uname,$udom) = @_;
-    my %name=&Apache::lonnet::get('environment', ['lastname','generation',
-						  'firstname','middlename'],
-                                  $udom,$uname);
-    my $fullname;
-    my ($tmp) = keys(%name);
-    if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
-        $fullname = &Apache::loncoursedata::ProcessFullName
-            (@name{qw/lastname generation firstname middlename/});
-    } else {
-        &Apache::lonnet::logthis('grades.pm: no name data for '.$uname.
-                                 '@'.$udom.':'.$tmp);
-    }
-    return $fullname;
-}
-
 #--- Format fullname, username:domain if different for display
 #--- Use anywhere where the student names are listed
 sub nameUserString {
@@ -992,7 +972,8 @@ sub sub_page_kw_js {
     my $request = shift;
     my $iconpath = $request->dir_config('lonIconsURL');
     &commonJSfunctions($request);
-    my $nothing= &Apache::lonhtmlcommon::javascript_nothing();
+    my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
+    $docopen=~s/^document\.//;
     $request->print(<<SUBJAVASCRIPT);
 <script type="text/javascript" language="javascript">
 
@@ -1105,7 +1086,7 @@ sub sub_page_kw_js {
     pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
     pWin.focus();
     pDoc = pWin.document;
-    pDoc.open($nothing,'replace');
+    pDoc.$docopen;
     pDoc.write("<html><head>");
     pDoc.write("<title>Message Central</title>");
 
@@ -1236,7 +1217,7 @@ sub sub_page_kw_js {
     hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
     hwdWin.focus();
     var hDoc = hwdWin.document;
-    hDoc.open($nothing,'replace');
+    hDoc.$docopen;
     hDoc.write("<html><head>");
     hDoc.write("<title>Highlight Central</title>");
 
@@ -1405,14 +1386,15 @@ sub submission {
     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'} = &get_fullname ($uname,$udom) if $ENV{'form.fullname'} eq '';
+    $ENV{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $ENV{'form.fullname'} eq '';
 
     my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }
 
     if (!&canview($usec)) {
 	$request->print('<font color="red">Unable to view requested student.('.
-			$uname.$udom.$usec.$ENV{'request.course.id'}.')</font>');
+			$uname.'@'.$udom.' in section '.$usec.' in course id '.
+			$ENV{'request.course.id'}.')</font>');
 	$request->print(&show_grading_menu_form($symb,$url));
 	return;
     }
@@ -1689,10 +1671,26 @@ KEYWORDS
 			$lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
 			    $display_part.' <font color="#999999">( ID '.$respid.
 			    ' )</font>&nbsp; &nbsp;';
+			my @files;
+			if ($record{"resource.$partid.$respid.portfiles"}) {
+			    my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
+			    foreach my $file (split(',',$record{"resource.$partid.$respid.portfiles"})) {
+				push(@files,$file_url.$file);
+			    
+				&Apache::lonnet::logthis("found a portfolio file".$record{"resource.$partid.$respid.portfiles"});
+				&Apache::lonnet::logthis("uploaded URL file".$record{"resource.$partid.$respid.uploadedurl"});
+			    }
+			}
 			if ($record{"resource.$partid.$respid.uploadedurl"}) {
-			    &Apache::lonnet::allowuploaded('/adm/grades',
-			      $record{"resource.$partid.$respid.uploadedurl"});
-			    $lastsubonly.='<a href="'.$record{"resource.$partid.$respid.uploadedurl"}.'" target="lonGRDs"><img src="/adm/lonIcons/unknown.gif" border=0"> File uploaded by student</a> <font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />';
+			    push(@files,$record{"resource.$partid.$respid.uploadedurl"});
+			}
+			if (@files) {
+			    $lastsubonly.='<br /><font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />';
+			    foreach my $file (@files) {
+				&Apache::lonnet::allowuploaded('/adm/grades',$file);
+				$lastsubonly.='<br /><a href="'.$file.'" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';
+			    }
+			    $lastsubonly.='<br />';
 			}
 			$lastsubonly.='<b>Submitted Answer: </b>'.
 			    &cleanRecord($subval,$responsetype,$symb,$partid,
@@ -2406,9 +2404,6 @@ sub viewgrades {
     my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'1');
     my $ctr = 0;
     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
-	my $uname = $_;
-	$uname=~s/:/_/;
-	$result.='<input type="hidden" name="ctr'.$ctr.'" value="'.$uname.'" />'."\n";
 	$ctr++;
 	$result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},
 				   $_,$$fullname{$_},\@parts,\%weight,$ctr);
@@ -2432,18 +2427,21 @@ sub viewstudentgrade {
     my ($uname,$udom) = split(/:/,$student);
     $student=~s/:/_/;
     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
-    my $result='<tr bgcolor="#ffffdd"><td align="right">'.$ctr.'&nbsp;</td><td>&nbsp;'.
+    my $result='<tr bgcolor="#ffffdd"><td align="right">'.
+	'<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> '.
 	'<font color="#999999">('.$uname.($ENV{'user.domain'} eq $udom ? '' : ':'.$udom).')</font></td>'."\n";
     foreach my $apart (@$parts) {
 	my ($part,$type) = &split_part_type($apart);
 	my $score=$record{"resource.$part.$type"};
+	$result.='<td align="middle">';
 	if ($type eq 'awarded') {
 	    my $pts = $score eq '' ? '' : $score*$$weight{$part};
 	    $result.='<input type="hidden" name="'.
 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
-	    $result.='<td align="middle"><input type="text" name="'.
+	    $result.='<input type="text" name="'.
 		'GD_'.$student.'_'.$part.'_awarded" '.
 		'onChange="javascript:changeSelect(\''.$part.'\',\''.$student.
 		'\')" value="'.$pts.'" size="4" /></td>'."\n";
@@ -2452,7 +2450,7 @@ sub viewstudentgrade {
 	    $status = 'nothing' if ($status eq '');
 	    $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
 		$part.'_solved_s" value="'.$status.'" />'."\n";
-	    $result.='<td align="middle">&nbsp;<select name="'.
+	    $result.='&nbsp;<select name="'.
 		'GD_'.$student.'_'.$part.'_solved" '.
 		'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
 	    $result.= (($status eq 'excused') ? '<option> </option><option selected="on">excused</option>' 
@@ -2463,7 +2461,7 @@ sub viewstudentgrade {
 	    $result.='<input type="hidden" name="'.
 		'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
 		    "\n";
-	    $result.='<td align="middle"><input type="text" name="'.
+	    $result.='<input type="text" name="'.
 		'GD_'.$student.'_'.$part.'_'.$type.'" '.
 		'value="'.$score.'" size="4" /></td>'."\n";
 	}
@@ -2653,24 +2651,26 @@ sub split_part_type {
 #
 #--- Javascript to handle csv upload
 sub csvupload_javascript_reverse_associate {
+    my $error1=&mt('You need to specify the username or ID');
+    my $error2=&mt('You need to specify at least one grading field');
   return(<<ENDPICK);
   function verify(vf) {
     var foundsomething=0;
     var founduname=0;
-    var founddomain=0;
+    var foundID=0;
     for (i=0;i<=vf.nfields.value;i++) {
       tw=eval('vf.f'+i+'.selectedIndex');
-      if (i==0 && tw!=0) { founduname=1; }
-      if (i==1 && tw!=0) { founddomain=1; }
-      if (i!=0 && i!=1 && tw!=0) { foundsomething=1; }
-    }
-    if (founduname==0 || founddomain==0) {
-      alert('You need to specify at both the username and domain');
-      return;
+      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 (founduname==0 && foundID==0) {
+	alert('$error1');
+	return;
     }
     if (foundsomething==0) {
-      alert('You need to specify at least one grading field');
-      return;
+	alert('$error2');
+	return;
     }
     vf.submit();
   }
@@ -2691,24 +2691,26 @@ ENDPICK
 }
 
 sub csvupload_javascript_forward_associate {
+    my $error1=&mt('You need to specify the username or ID');
+    my $error2=&mt('You need to specify at least one grading field');
   return(<<ENDPICK);
   function verify(vf) {
     var foundsomething=0;
     var founduname=0;
-    var founddomain=0;
+    var foundID=0;
     for (i=0;i<=vf.nfields.value;i++) {
       tw=eval('vf.f'+i+'.selectedIndex');
-      if (tw==1) { founduname=1; }
-      if (tw==2) { founddomain=1; }
-      if (tw>2) { foundsomething=1; }
-    }
-    if (founduname==0 || founddomain==0) {
-      alert('You need to specify at both the username and domain');
-      return;
+      if (tw==1) { foundID=1; }
+      if (tw==2) { founduname=1; }
+      if (tw>3) { foundsomething=1; }
+    }
+    if (founduname==0 && foundID==0) {
+	alert('$error1');
+	return;
     }
     if (foundsomething==0) {
-      alert('You need to specify at least one grading field');
-      return;
+	alert('$error2');
+	return;
     }
     vf.submit();
   }
@@ -2735,7 +2737,8 @@ sub csvuploadmap_header {
     }
 
     my ($result) = &showResourceInfo($url,$ENV{'form.probTitle'});
-
+    my $checked=(($ENV{'form.noFirstLine'})?' checked="checked"':'');
+    my $ignore=&mt('Ignore First Line');
     $request->print(<<ENDPICK);
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <h3><font color="#339933">Uploading Class Grades</font></h3>
@@ -2746,6 +2749,7 @@ Total number of records found in file: $
 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>
 <input type="hidden" name="associate"  value="" />
 <input type="hidden" name="phase"      value="three" />
 <input type="hidden" name="datatoken"  value="$datatoken" />
@@ -2757,7 +2761,7 @@ to this page if the data selected is ins
 <input type="hidden" name="url"        value="$url" />
 <input type="hidden" name="saveState"  value="$ENV{'form.saveState'}" />
 <input type="hidden" name="probTitle"  value="$ENV{'form.probTitle'}" />
-<input type="hidden" name="command"    value="csvuploadassign" />
+<input type="hidden" name="command"    value="csvuploadoptions" />
 <hr />
 <script type="text/javascript" language="Javascript">
 $javascript
@@ -2770,13 +2774,18 @@ ENDPICK
 sub csvupload_fields {
     my ($url,$symb) = @_;
     my (@parts) = &getpartlist($url,$symb);
-    my @fields=(['username','Student Username'],['domain','Student Domain']);
+    my @fields=(['ID','Student ID'],
+		['username','Student Username'],
+		['domain','Student Domain']);
     foreach my $part (sort(@parts)) {
 	my @datum;
 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
 	my $name=$part;
 	if  (!$display) { $display = $name; }
 	@datum=($name,$display);
+	if ($name=~/^stores_(.*)_awarded/) {
+	    push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
+	}
 	push(@fields,\@datum);
     }
     return (@fields);
@@ -2817,6 +2826,7 @@ CSVFORMJS
 	'.</b></td></tr>'."\n";
     $result.='<tr bgcolor=#ffffe6><td>'."\n";
     my $upfile_select=&Apache::loncommon::upfile_select_html();
+    my $ignore=&mt('Ignore First Line');
     $result.=<<ENDUPFORM;
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <input type="hidden" name="symb" value="$symb" />
@@ -2826,7 +2836,7 @@ CSVFORMJS
 <input type="hidden" name="saveState"  value="$ENV{'form.saveState'}" />
 $upfile_select
 <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scores" />
-
+<label><input type="checkbox" name="noFirstLine" />$ignore</lable>
 </form>
 ENDUPFORM
     $result.='</td></tr></table>'."\n";
@@ -2849,6 +2859,7 @@ sub csvuploadmap {
 	&Apache::loncommon::load_tmp_file($request);
     }
     my @records=&Apache::loncommon::upfile_record_sep();
+    if ($ENV{'form.noFirstLine'}) { shift(@records); }
     &csvuploadmap_header($request,$symb,$url,$datatoken,$#records+1);
     my ($i,$keyfields);
     if (@records) {
@@ -2874,14 +2885,51 @@ sub csvuploadmap {
     return '';
 }
 
-sub csvuploadassign {
+sub csvuploadoptions {
     my ($request)= @_;
     my ($symb,$url)=&get_symb_and_url($request);
-    if (!$symb) {return '';}
-    &Apache::loncommon::load_tmp_file($request);
-    my @gradedata = &Apache::loncommon::upfile_record_sep();
+    my $checked=(($ENV{'form.noFirstLine'})?'1':'0');
+    my $ignore=&mt('Ignore First Line');
+    $request->print(<<ENDPICK);
+<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
+<h3><font color="#339933">Uploading Class Grade Options</font></h3>
+<input type="hidden" name="command"    value="csvuploadassign" />
+<input type="submit" value="Assign Grades" /><br />
+<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
+</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");
+    }
+    foreach my $key (sort(keys(%ENV))) {
+	if ($key !~ /^form\.(.*)$/) { next; }
+	my $cleankey=$1;
+	if ($cleankey eq 'command') { next; }
+	$request->print('<input type="hidden" name="'.$cleankey.
+			'"  value="'.$ENV{$key}.'" />'."\n");
+    }
+    # FIXME do a check for any duplicated user ids...
+    # FIXME do a check for any invalid user ids?...
+    $request->print("<hr /></form>\n");
+    $request->print(&show_grading_menu_form($symb,$url));
+    return '';
+}
+
+sub get_fields {
+    my %fields;
     my @keyfields = split(/\,/,$ENV{'form.keyfields'});
-    my %fields=();
     for (my $i=0; $i<=$ENV{'form.nfields'}; $i++) {
 	if ($ENV{'form.upfile_associate'} eq 'reverse') {
 	    if ($ENV{'form.f'.$i} ne 'none') {
@@ -2893,6 +2941,17 @@ sub csvuploadassign {
 	    }
 	}
     }
+    return %fields;
+}
+
+sub csvuploadassign {
+    my ($request)= @_;
+    my ($symb,$url)=&get_symb_and_url($request);
+    if (!$symb) {return '';}
+    &Apache::loncommon::load_tmp_file($request);
+    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);
@@ -2901,12 +2960,29 @@ sub csvuploadassign {
     my $countdone=0;
     foreach my $grade (@gradedata) {
 	my %entries=&Apache::loncommon::record_sep($grade);
+	my $domain;
+	if ($entries{$fields{'domain'}}) {
+	    $domain=$entries{$fields{'domain'}};
+	} else {
+	    $domain=$ENV{'form.default_domain'};
+	}
+	$domain=~s/\s//g;
 	my $username=$entries{$fields{'username'}};
 	$username=~s/\s//g;
-	my $domain=$entries{$fields{'domain'}};
-	$domain=~s/\s//g;
+	if (!$username) {
+	    my $id=$entries{$fields{'ID'}};
+	    $id=~s/\s//g;
+	    my %ids=&Apache::lonnet::idget($domain,$id);
+	    $username=$ids{$id};
+	}
 	if (!exists($$classlist{"$username:$domain"})) {
-	    push(@skipped,"$username:$domain");
+	    my $id=$entries{$fields{'ID'}};
+	    $id=~s/\s//g;
+	    if ($id) {
+		push(@skipped,"$id:$domain");
+	    } else {
+		push(@skipped,"$username:$domain");
+	    }
 	    next;
 	}
 	my $usec=$classlist->{"$username:$domain"}[5];
@@ -2914,16 +2990,34 @@ sub csvuploadassign {
 	    push(@notallowed,"$username:$domain");
 	    next;
 	}
+	my %points;
 	my %grades;
 	foreach my $dest (keys(%fields)) {
-	    if ($dest eq 'username' || $dest eq 'domain') { next; }
-	    if ($entries{$fields{$dest}} eq '') { next; }
-	    my $store_key=$dest;
-	    $store_key=~s/^stores/resource/;
-	    $store_key=~s/_/\./g;
-	    $grades{$store_key}=$entries{$fields{$dest}};
+	    if ($dest eq 'ID' || $dest eq 'username' ||
+		$dest eq 'domain') { next; }
+	    if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
+	    if ($dest=~/stores_(.*)_points/) {
+		my $part=$1;
+		my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
+					      $symb,$domain,$username);
+		$entries{$fields{$dest}}=~s/\s//g;
+		my $pcr=$entries{$fields{$dest}} / $wgt;
+		my $award='correct_by_override';
+		$grades{"resource.$part.awarded"}=$pcr;
+		$grades{"resource.$part.solved"}=$award;
+		$points{$part}=1;
+	    } else {
+		if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
+		if ($dest=~/stores_(.*)_solved/)  { if ($points{$1}) {next;} }
+		my $store_key=$dest;
+		$store_key=~s/^stores/resource/;
+		$store_key=~s/_/\./g;
+		$grades{$store_key}=$entries{$fields{$dest}};
+	    }
 	}
+	if (! %grades) { push(@skipped,"$username:$domain no data to store"); }
 	$grades{"resource.regrader"}="$ENV{'user.name'}:$ENV{'user.domain'}";
+#	&Apache::lonnet::logthis(" storing ".(join('-',%grades)));
 	&Apache::lonnet::cstore(\%grades,$symb,$ENV{'request.course.id'},
 				$domain,$username);
 	$request->print('.');
@@ -3062,7 +3156,8 @@ sub getSymbMap {
     my $minder = 0;
 
     # Gather every sequence that has problems.
-    my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); }, 1);
+    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) ) {
 	    my $title = $minder.'.'.$sequence->compTitle();
@@ -3071,8 +3166,6 @@ sub getSymbMap {
 	    $minder++;
 	}
     }
-
-    $navmap->untieHashes();
     return \@titles,\%symbx;
 }
 
@@ -3143,7 +3236,7 @@ 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_problem() && !$curRes->randomout) {
 	    my $parts = $curRes->parts();
             my $title = $curRes->compTitle();
 	    my $symbx = $curRes->symb();
@@ -3205,8 +3298,6 @@ sub displayPage {
         $curRes = $iterator->next();
     }
 
-    $navmap->untieHashes();
-
     $studentTable.='</td></tr></table></td></tr></table>'."\n".
 	'<input type="button" value="Save" '.
 	'onClick="javascript:checkSubmitPage(this.form,'.$question.');" TARGET=_self />'.
@@ -3219,9 +3310,12 @@ sub displayPage {
 
 sub displaySubByDates {
     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
+    my $isCODE=0;
+    if (exists($record->{'resource.CODE'})) { $isCODE=1; }
     my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'.
 	'<table border="0" width="100%"><tr bgcolor="#e6ffff">'.
 	'<td><b>Date/Time</b></td>'.
+	($isCODE?'<td><b>CODE</b></td>':'').
 	'<td><b>Submission</b></td>'.
 	'<td><b>Status&nbsp;</b></td></tr>';
     my ($version);
@@ -3234,6 +3328,9 @@ sub displaySubByDates {
     for ($version=1;$version<=$$record{'version'};$version++) {
 	my $timestamp = scalar(localtime($$record{$version.':timestamp'}));
 	$studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';
+	if ($isCODE) {
+	    $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
+	}
 	my @versionKeys = split(/\:/,$$record{$version.':keys'});
 	my @displaySub = ();
 	foreach my $partid (@{$parts}) {
@@ -3402,8 +3499,6 @@ sub updateGradeByPage {
         $curRes = $iterator->next();
     }
 
-    $navmap->untieHashes();
-
     $studentTable.='</td></tr></table></td></tr></table>';
     $studentTable.=&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'});
     my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
@@ -3493,7 +3588,7 @@ sub scantron_CODElist {
     my $cnum = $ENV{'course.'.$ENV{'request.course.id'}.'.num'};
     my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
     my $namechoice='<option></option>';
-    foreach my $name (sort(@names)) {
+    foreach my $name (sort {uc($a) cmp uc($b)} @names) {
 	if ($name =~ /^error: 2 /) { next; }
 	$namechoice.='<option value="'.$name.'">'.$name.'</option>';
     }
@@ -3530,8 +3625,8 @@ sub scantron_selectphase {
     $result.= <<SCANTRONFORM;
     <table width="100%" border="0">
     <tr>
+     <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
       <td bgcolor="#777777">
-       <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
        <input type="hidden" name="command" value="scantron_warning" />
         $default_form_data
         <table width="100%" border="0">
@@ -3568,8 +3663,8 @@ sub scantron_selectphase {
             </td>
           </tr>
         </table>
-       </form>
-      </td>
+       </td>
+     </form>
     </tr>
 SCANTRONFORM
    
@@ -3625,8 +3720,8 @@ SCANTRONFORM
     }
     $r->print(<<SCANTRONFORM);
     <tr>
-      <td bgcolor="#777777">
-        <form action='/adm/grades' name='scantron_download'>
+      <form action='/adm/grades' name='scantron_download'>
+        <td bgcolor="#777777">
           <input type="hidden" name="command" value="scantron_download" />
           <table width="100%" border="0">
             <tr bgcolor="#e6ffff">
@@ -3643,14 +3738,13 @@ SCANTRONFORM
               </td>
             </tr>
           </table>
-        </form>
-      </td>
+        </td>
+      </form>
     </tr>
 SCANTRONFORM
 
     $r->print(<<SCANTRONFORM);
   </table>
-</form>
 $grading_menu_button
 SCANTRONFORM
 
@@ -3799,25 +3893,49 @@ sub scantron_parse_scanline {
 	my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});
 	substr($questions,0,$$scantron_config{'Qlength'})='';
 	if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }
-	my @array=split($$scantron_config{'Qon'},$currentquest,-1);
-	if (length($array[0]) eq $$scantron_config{'Qlength'}) {
-	    $record{"scantron.$questnum.answer"}='';
-	    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
-		push(@{$record{"scantron.missingerror"}},$questnum);
- 	    }
+	if ($$scantron_config{'Qon'} eq 'letter') {
+	    if (!$currentquest || $currentquest eq $$scantron_config{'Qoff'} ||
+		$currentquest !~ /^[A-Z]$/) {
+		$record{"scantron.$questnum.answer"}='';
+		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
+		    push(@{$record{"scantron.missingerror"}},$questnum);
+		}
+	    } else {
+		$record{"scantron.$questnum.answer"}=$currentquest;
+	    }
+	} elsif ($$scantron_config{'Qon'} eq 'number') {
+	    if (!$currentquest || $currentquest eq $$scantron_config{'Qoff'} ||
+		$currentquest !~ /^\d$/) {
+		$record{"scantron.$questnum.answer"}='';
+		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
+		    push(@{$record{"scantron.missingerror"}},$questnum);
+		}
+	    } else {
+		$record{"scantron.$questnum.answer"}=
+		    $alphabet[$currentquest-1];
+	    }
 	} else {
-	    $record{"scantron.$questnum.answer"}=$alphabet[length($array[0])];
+	    my @array=split($$scantron_config{'Qon'},$currentquest,-1);
+	    if (length($array[0]) eq $$scantron_config{'Qlength'}) {
+		$record{"scantron.$questnum.answer"}='';
+		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
+		    push(@{$record{"scantron.missingerror"}},$questnum);
+		}
+	    } else {
+		$record{"scantron.$questnum.answer"}=
+		    $alphabet[length($array[0])];
+	    }
+	    if (scalar(@array) gt 2) {
+		push(@{$record{'scantron.doubleerror'}},$questnum);
+		my @ans=@array;
+		my $i=length($ans[0]);shift(@ans);
+		while ($#ans) {
+		    $i+=length($ans[0])+1;
+		    $record{"scantron.$questnum.answer"}.=$alphabet[$i];
+		    shift(@ans);
+		}
+	    }
 	}
- 	if (scalar(@array) gt 2) {
- 	    push(@{$record{'scantron.doubleerror'}},$questnum);
- 	    my @ans=@array;
- 	    my $i=length($ans[0]);shift(@ans);
-	    while ($#ans) {
- 		$i+=length($ans[0])+1;
- 		$record{"scantron.$questnum.answer"}.=$alphabet[$i];
- 		shift(@ans);
- 	    }
- 	}
     }
     $record{'scantron.maxquest'}=$questnum;
     return \%record;
@@ -3982,14 +4100,28 @@ sub scantron_do_warning {
     if (!$symb) {return '';}
     my $default_form_data=&defaultFormData($symb,$url);
     $r->print(&scantron_form_start().$default_form_data);
-    my $warning=&scantron_warning_screen('Validate Records');
-    $r->print(<<STUFF);
+    if ( $ENV{'form.selectpage'} eq '' ||
+	 $ENV{'form.scantron_selectfile'} eq '' ||
+	 $ENV{'form.scantron_format'} eq '' ) {
+	$r->print("<p>You have forgetten to specify some information. Please go Back and try again.</p>");
+	if ( $ENV{'form.selectpage'} eq '') {
+	    $r->print('<p><font color="red">You have not selected a Sequence to grade</font></p>');
+	} 
+	if ( $ENV{'form.scantron_selectfile'} eq '') {
+	    $r->print('<p><font color="red">You have not selected a file that contains the student\'s response data.</font></p>');
+	} 
+	if ( $ENV{'form.scantron_format'} eq '') {
+	    $r->print('<p><font color="red">You have not selected a the format of the student\'s response data.</font></p>');
+	} 
+    } else {
+	my $warning=&scantron_warning_screen('Validate Records');
+	$r->print(<<STUFF);
 $warning
 <input type="submit" name="submit" value="Validate Records" />
 <input type="hidden" name="command" value="scantron_validate" />
-</form>
 STUFF
-    $r->print("<br />".&show_grading_menu_form($symb,$url)."</body></html>");
+    }
+    $r->print("</form><br />".&show_grading_menu_form($symb,$url)."</body></html>");
     return '';
 }
 
@@ -4000,7 +4132,7 @@ sub scantron_form_start {
   <input type="hidden" name="selectpage" value="$ENV{'form.selectpage'}" />
   <input type="hidden" name="scantron_format" value="$ENV{'form.scantron_format'}" />
   <input type="hidden" name="scantron_selectfile" value="$ENV{'form.scantron_selectfile'}" />
-  <input type="hidden" name="scantron_maxbubble" value="$max_bubble'" />
+  <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
   <input type="hidden" name="scantron_CODElist" value="$ENV{'form.scantron_CODElist'}" />
   <input type="hidden" name="scantron_CODEunique" value="$ENV{'form.scantron_CODEunique'}" />
   <input type="hidden" name="scantron_options_redo" value="$ENV{'form.scantron_options_redo'}" />
@@ -4290,6 +4422,12 @@ sub scantron_get_correction {
 	$r->print(" in scanline $i <pre>".
 		  $line."</pre> \n");
     }
+    my $message="<p>The ID on the form is  <tt>".
+	$$scan_record{'scantron.ID'}."</tt><br />\n".
+	"The name on the paper is ".
+	$$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");
     if ($error =~ /ID$/) {
@@ -4298,11 +4436,7 @@ sub scantron_get_correction {
 	} elsif ($error eq 'duplicateID') {
 	    $r->print("The encoded ID has also been used by a previous paper $arg</p>\n");
 	}
-	$r->print("<p>The ID on the form is  <tt>".
-		  $$scan_record{'scantron.ID'}."</tt><br />\n");
-	$r->print("The name on the paper is ".
-		  $$scan_record{'scantron.LastName'}.",".
-		  $$scan_record{'scantron.FirstName'}."</p>");
+	$r->print($message);
 	$r->print("<p>How should I handle this? <br /> \n");
 	$r->print("\n<ul><li> ");
 	#FIXME it would be nice if this sent back the user ID and
@@ -4320,13 +4454,9 @@ sub scantron_get_correction {
 	} elsif ($error eq 'duplicateCODE') {
 	    $r->print("</p><p>The encoded CODE has also been used by a previous paper ".join(', ',@{$arg}).", and CODEs are supposed to be unique</p>\n");
 	}
-	$r->print("<p>The CODE on the form is  <tt>".
-		  $$scan_record{'scantron.CODE'}."</tt><br />\n");
-	$r->print("<p>The ID on the form is  <tt>".
-		  $$scan_record{'scantron.ID'}."</tt><br />\n");
-	$r->print("The name on the paper is ".
-		  $$scan_record{'scantron.LastName'}.",".
-		  $$scan_record{'scantron.FirstName'}."</p>");
+	$r->print("<p>The CODE on the form is  <tt>'".
+		  $$scan_record{'scantron.CODE'}."'</tt><br />\n");
+	$r->print($message);
 	$r->print("<p>How should I handle this? <br /> \n");
 	$r->print("\n<br /> ");
 	my $i=0;
@@ -4369,6 +4499,7 @@ ENDSCRIPT
 	$r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n");
 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 		  join(',',@{$arg}).'" />');
+	$r->print($message);
 	$r->print("<p>Please indicate which bubble should be used for grading</p>");
 	foreach my $question (@{$arg}) {
 	    my $selected=$$scan_record{"scantron.$question.answer"};
@@ -4376,6 +4507,7 @@ ENDSCRIPT
 	}
     } elsif ($error eq 'missingbubble') {
 	$r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");
+	$r->print($message);
 	$r->print("<p>Please indicate which bubble should be used for grading</p>");
 	$r->print("Some questions have no scanned bubbles\n");
 	$r->print('<input type="hidden" name="scantron_questions" value="'.
@@ -4467,7 +4599,14 @@ sub scantron_validate_CODE {
 						 $scan_data);
 	my $CODE=$$scan_record{'scantron.CODE'};
 	my $error=0;
-	if (!exists($allcodes{$CODE}) && !$$scan_record{'scantron.useCODE'}) {
+	if (!&Apache::lonnet::validCODE($CODE)) {
+	    &scantron_get_correction($r,$i,$scan_record,
+				     \%scantron_config,
+				     $line,'incorrectCODE',\%allcodes);
+	    return(1,$currentphase);
+	}
+	if (%allcodes && !exists($allcodes{$CODE}) 
+	    && !$$scan_record{'scantron.useCODE'}) {
 	    &scantron_get_correction($r,$i,$scan_record,
 				     \%scantron_config,
 				     $line,'incorrectCODE',\%allcodes);
@@ -4522,7 +4661,7 @@ sub scantron_get_maxbubble {
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
     &Apache::lonnet::delenv('form.counter');
     foreach my $resource (@resources) {
-	my $result=&Apache::lonnet::ssi($resource->src());
+	my $result=&Apache::lonnet::ssi($resource->src().'?symb='.&Apache::lonnet::escape($resource->symb()));
     }
     &Apache::lonnet::delenv('scantron\.');
     my $envfile=$ENV{'user.environment'};
@@ -4639,8 +4778,14 @@ SCANTRONFORM
 	    if (exists($scan_record->{'scantron.CODE'}) &&
 		$scan_record->{'scantron.CODE'}) {
 		$form{'CODE'}=$scan_record->{'scantron.CODE'};
+	    } else {
+		$form{'CODE'}='';
 	    }
 	    my $result=&Apache::lonnet::ssi($resource->src(),%form);
+	    if ($result ne '') {
+		&Apache::lonnet::logthis("scantron grading error -> $result");
+		&Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $ENV{'request.course.id'} url ".$resource->src());
+	    }
 	    if (&Apache::loncommon::connection_aborted($r)) { last; }
 	}
 	$completedstudents{$uname}={'line'=>$line};
@@ -4653,7 +4798,6 @@ SCANTRONFORM
 #    my $lasttime = &Time::HiRes::time()-$start;
 #    $r->print("<p>took $lasttime</p>");
 
-    $navmap->untieHashes();
     $r->print("</form>");
     $r->print(&show_grading_menu_form($symb,$url));
     return '';
@@ -4801,7 +4945,6 @@ DOWNLOAD
 #
 #-------------------------------------------------------------------
 
-
 #-------------------------- Menu interface -------------------------
 #
 #--- Show a Grading Menu button - Calls the next routine ---
@@ -4854,6 +4997,7 @@ sub gradingmenu {
 	    if (!checkReceiptNo(formname,'notOK')) { return false;}
 	    formname.submit();
 	}
+	if (val < 7) formname.submit();
     }
 
     function checkReceiptNo(formname,nospace) {
@@ -4906,7 +5050,7 @@ GRADINGMENUJS
 		($saveSec eq $_ ? 'selected="on"':'').'>'.$_.'</option>'."\n";
 	}
     }
-    $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="on"' : ''). '>all</select> &nbsp; ';
+    $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="on"' : ''). '>all</option></select> &nbsp; ';
 
     $result.=&mt('Student Status').':</b>'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef);
 
@@ -4958,6 +5102,9 @@ GRADINGMENUJS
 	    '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')">'.
 	    '</td></tr>'."\n";
     } 
+    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
+	'<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'.
+	'" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n";
 
     $result.='</form></td></tr></table>'."\n".
 	'</td></tr></table>'."\n".
@@ -5057,9 +5204,9 @@ sub handler {
 	    $request->print(&csvupload($request));
 	} elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
 	    $request->print(&csvuploadmap($request));
-	} elsif ($command eq 'csvuploadassign' && $perm{'mgr'}) {
+	} elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
 	    if ($ENV{'form.associate'} ne 'Reverse Association') {
-		$request->print(&csvuploadassign($request));
+		$request->print(&csvuploadoptions($request));
 	    } else {
 		if ( $ENV{'form.upfile_associate'} ne 'reverse' ) {
 		    $ENV{'form.upfile_associate'} = 'reverse';
@@ -5068,6 +5215,8 @@ sub handler {
 		}
 		$request->print(&csvuploadmap($request));
 	    }
+	} elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
+	    $request->print(&csvuploadassign($request));
 	} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
 	    $request->print(&scantron_selectphase($request));
  	} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {