--- loncom/homework/grades.pm	2007/06/15 21:21:16	1.405
+++ loncom/homework/grades.pm	2007/10/25 20:05:52	1.464
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.405 2007/06/15 21:21:16 www Exp $
+# $Id: grades.pm,v 1.464 2007/10/25 20:05:52 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -35,47 +35,115 @@ use Apache::loncommon;
 use Apache::lonhtmlcommon;
 use Apache::lonnavmaps;
 use Apache::lonhomework;
+use Apache::lonpickcode;
 use Apache::loncoursedata;
 use Apache::lonmsg();
 use Apache::Constants qw(:common);
 use Apache::lonlocal;
 use Apache::lonenc;
 use String::Similarity;
-use lib '/home/httpd/lib/perl';
 use LONCAPA;
 
 use POSIX qw(floor);
 
-my %oldessays=();
+
 my %perm=();
+my %bubble_lines_per_response = ();     # no. bubble lines for each response.
+                                   # index is "symb.part_id"
+
+my %first_bubble_line = ();	# First bubble line no. for each bubble.
+
+# Save and restore the bubble lines array to the form env.
+
+
+sub save_bubble_lines {
+    foreach my $line (keys(%bubble_lines_per_response)) {
+	$env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
+	$env{"form.scantron.first_bubble_line.$line"} =
+	    $first_bubble_line{$line};
+    }
+}
+
+
+sub restore_bubble_lines {
+    my $line = 0;
+    %bubble_lines_per_response = ();
+    while ($env{"form.scantron.bubblelines.$line"}) {
+	my $value = $env{"form.scantron.bubblelines.$line"};
+	$bubble_lines_per_response{$line} = $value;
+	$first_bubble_line{$line}  =
+	    $env{"form.scantron.first_bubble_line.$line"};
+	$line++;
+    }
+
+}
+
+#  Given the parsed scanline, get the response for 
+#  'answer' number n:
+
+sub get_response_bubbles {
+    my ($parsed_line, $response)  = @_;
+
+
+    my $bubble_line = $first_bubble_line{$response-1} +1;
+    my $bubble_lines= $bubble_lines_per_response{$response-1};
+    
+    my $selected = "";
+
+    for (my $bline = 0; $bline < $bubble_lines; $bline++) {
+	$selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":";
+	$bubble_line++;
+    }
+    return $selected;
+}
+
 
 # ----- These first few routines are general use routines.----
+
+# Return the number of occurences of a pattern in a string.
+
+sub occurence_count {
+    my ($string, $pattern) = @_;
+
+    my @matches = ($string =~ /$pattern/g);
+
+    return scalar(@matches);
+}
+
+
+# Take a string known to have digits and convert all the
+# digits into letters in the range J,A..I.
+
+sub digits_to_letters {
+    my ($input) = @_;
+
+    my @alphabet = ('J', 'A'..'I');
+
+    my @input    = split(//, $input);
+    my $output ='';
+    for (my $i = 0; $i < scalar(@input); $i++) {
+	if ($input[$i] =~ /\d/) {
+	    $output .= $alphabet[$input[$i]];
+	} else {
+	    $output .= $input[$i];
+	}
+    }
+    return $output;
+}
+
 #
 # --- Retrieve the parts from the metadata file.---
 sub getpartlist {
     my ($symb) = @_;
-    my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
-    my $partorder = &Apache::lonnet::metadata($url, 'partorder');
-    my @parts;
-    if ($partorder) {
-	for my $part (split (/,/,$partorder)) {
-	    if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) {
-		push(@parts, $part);
-	    }
-	}	    
-    } else {
-	my $metadata = &Apache::lonnet::metadata($url, 'packages');
-	foreach (split(/\,/,$metadata)) {
-	    if ($_ =~ /^part_(.*)$/) {
-		if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) {
-		    push(@parts, $1);
-		}
-	    }
-	}
-    }
+
+    my $navmap   = Apache::lonnavmaps::navmap->new();
+    my $res      = $navmap->getBySymb($symb);
+    my $partlist = $res->parts();
+    my $url      = $res->src();
+    my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
+
     my @stores;
-    foreach my $part (@parts) {
-	my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
+    foreach my $part (@{ $partlist }) {
 	foreach my $key (@metakeys) {
 	    if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
 	}
@@ -94,6 +162,7 @@ sub get_symb {
 	    return ();
 	}
     }
+    &Apache::lonenc::check_decrypt(\$symb);
     return ($symb);
 }
 
@@ -195,22 +264,54 @@ sub showResourceInfo {
     return $result,$responseType,$hdgrade,$partlist,$handgrade;
 }
 
+sub reset_caches {
+    &reset_analyze_cache();
+    &reset_perm();
+}
+
+{
+    my %analyze_cache;
 
-sub get_order {
-    my ($partid,$respid,$symb,$uname,$udom)=@_;
-    my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
-    $url=&Apache::lonnet::clutter($url);
-    my $subresult=&Apache::lonnet::ssi($url,
-				       ('grade_target' => 'analyze'),
-				       ('grade_domain' => $udom),
-				       ('grade_symb' => $symb),
-				       ('grade_courseid' => 
-					        $env{'request.course.id'}),
-				       ('grade_username' => $uname));
-    (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
-    my %analyze=&Apache::lonnet::str2hash($subresult);
-    return ($analyze{"$partid.$respid.shown"});
+    sub reset_analyze_cache {
+	undef(%analyze_cache);
+    }
+
+    sub get_analyze {
+	my ($symb,$uname,$udom)=@_;
+	my $key = "$symb\0$uname\0$udom";
+	return $analyze_cache{$key} if (exists($analyze_cache{$key}));
+
+	my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
+	$url=&Apache::lonnet::clutter($url);
+	my $subresult=&Apache::lonnet::ssi($url,
+					   ('grade_target' => 'analyze'),
+					   ('grade_domain' => $udom),
+					   ('grade_symb' => $symb),
+					   ('grade_courseid' => 
+					    $env{'request.course.id'}),
+					   ('grade_username' => $uname));
+	(undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
+	my %analyze=&Apache::lonnet::str2hash($subresult);
+	return $analyze_cache{$key} = \%analyze;
+    }
+
+    sub get_order {
+	my ($partid,$respid,$symb,$uname,$udom)=@_;
+	my $analyze = &get_analyze($symb,$uname,$udom);
+	return $analyze->{"$partid.$respid.shown"};
+    }
+
+    sub get_radiobutton_correct_foil {
+	my ($partid,$respid,$symb,$uname,$udom)=@_;
+	my $analyze = &get_analyze($symb,$uname,$udom);
+	foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) {
+	    if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
+		return $foil;
+	    }
+	}
+    }
 }
+
 #--- Clean response type for display
 #--- Currently filters option/rank/radiobutton/match/essay/Task
 #        response types only.
@@ -259,11 +360,11 @@ sub cleanRecord {
     } elsif ($response eq 'radiobutton') {
 	my %answer=&Apache::lonnet::str2hash($answer);
 	my ($toprow,$bottomrow);
-	my $correct=($order->[0])+1;
-	for (my $i=1;$i<=$#$order;$i++) {
-	    my $foil=$order->[$i];
+	my $correct = 
+	    &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);
+	foreach my $foil (@$order) {
 	    if (exists($answer{$foil})) {
-		if ($i == $correct) {
+		if ($foil eq $correct) {
 		    $toprow.='<td><b>true</b></td>';
 		} else {
 		    $toprow.='<td><i>true</i></td>';
@@ -327,7 +428,10 @@ sub cleanRecord {
 	    $result.='</ul>';
 	    return $result;
 	}
-       
+    } elsif ( $response =~ m/(?:numerical|formula)/) {
+	$answer = 
+	    &Apache::loncommon::format_previous_attempt_value('submission',
+							      $answer);
     }
     return $answer;
 }
@@ -371,8 +475,10 @@ COMMONJSFUNCTIONS
 #--- Dumps the class list with usernames,list of sections,
 #--- section, ids and fullnames for each user.
 sub getclasslist {
-    my ($getsec,$filterlist) = @_;
+    my ($getsec,$filterlist,$getgroup) = @_;
     my @getsec;
+    my @getgroup;
+    my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     if (!ref($getsec)) {
 	if ($getsec ne '' && $getsec ne 'all') {
 	    @getsec=($getsec);
@@ -381,10 +487,19 @@ sub getclasslist {
 	@getsec=@{$getsec};
     }
     if (grep(/^all$/,@getsec)) { undef(@getsec); }
+    if (!ref($getgroup)) {
+	if ($getgroup ne '' && $getgroup ne 'all') {
+	    @getgroup=($getgroup);
+	}
+    } else {
+	@getgroup=@{$getgroup};
+    }
+    if (grep(/^all$/,@getgroup)) { undef(@getgroup); }
 
-    my $classlist=&Apache::loncoursedata::get_classlist();
+    my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist();
     # Bail out if we were unable to get the classlist
     return if (! defined($classlist));
+    &Apache::loncoursedata::get_group_memberships($classlist,$keylist);
     #
     my %sections;
     my %fullnames;
@@ -401,18 +516,40 @@ sub getclasslist {
             $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
         my $status   = 
             $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
+        my $group   = 
+            $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
 	# filter students according to status selected
-	if ($filterlist && $env{'form.Status'} ne 'Any') {
-	    if ($env{'form.Status'} ne $status) {
-		delete ($classlist->{$student});
+	if ($filterlist && (!($stu_status =~ /Any/))) {
+	    if (!($stu_status =~ $status)) {
+		delete($classlist->{$student});
 		next;
 	    }
 	}
+	# filter students according to groups selected
+	my @stu_groups = split(/,/,$group);
+	if (@getgroup) {
+	    my $exclude = 1;
+	    foreach my $grp (@getgroup) {
+	        foreach my $stu_group (@stu_groups) {
+	            if ($stu_group eq $grp) {
+	                $exclude = 0;
+    	            } 
+	        }
+    	        if (($grp eq 'none') && !$group) {
+        	        $exclude = 0;
+        	}
+	    }
+	    if ($exclude) {
+	        delete($classlist->{$student});
+	    }
+	}
 	$section = ($section ne '' ? $section : 'none');
 	if (&canview($section)) {
 	    if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
 		$sections{$section}++;
-		$fullnames{$student}=$fullname;
+		if ($classlist->{$student}) {
+		    $fullnames{$student}=$fullname;
+		}
 	    } else {
 		delete($classlist->{$student});
 	    }
@@ -485,6 +622,7 @@ sub student_gradeStatus {
 # Shows a student's view of problem and submission
 sub jscriptNform {
     my ($symb) = @_;
+    my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     my $jscript='<script type="text/javascript" language="javascript">'."\n".
 	'    function viewOneStudent(user,domain) {'."\n".
 	'	document.onestudent.student.value = user;'."\n".
@@ -493,10 +631,10 @@ sub jscriptNform {
 	'    }'."\n".
 	'</script>'."\n";
     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
-	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
-	'<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
+	'<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
 	'<input type="hidden" name="command" value="submission" />'."\n".
 	'<input type="hidden" name="student" value="" />'."\n".
 	'<input type="hidden" name="userdom" value="" />'."\n".
@@ -504,6 +642,8 @@ sub jscriptNform {
     return $jscript;
 }
 
+
+
 # Given the score (as a number [0-1] and the weight) what is the final
 # point value? This function will round to the nearest tenth, third,
 # or quarter if one of those is within the tolerance of .00001.
@@ -538,7 +678,7 @@ sub compute_points {
 #
 
 sub most_similar {
-    my ($uname,$udom,$uessay)=@_;
+    my ($uname,$udom,$uessay,$old_essays)=@_;
 
 # ignore spaces and punctuation
 
@@ -555,23 +695,22 @@ sub most_similar {
     my $scrsid='';
     my $sessay='';
 # go through all essays ...
-    foreach my $tkey (keys %oldessays) {
-	my ($tname,$tdom,$tcrsid)=split(/\./,$tkey);
+    foreach my $tkey (keys(%$old_essays)) {
+	my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
 # ... except the same student
-        if (($tname ne $uname) || ($tdom ne $udom)) {
-	    my $tessay=$oldessays{$tkey};
-            $tessay=~s/\W+/ /gs;
+        next if (($tname eq $uname) && ($tdom eq $udom));
+	my $tessay=$old_essays->{$tkey};
+	$tessay=~s/\W+/ /gs;
 # String similarity gives up if not even limit
-            my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
+	my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
 # Found one
-            if ($tsimilar>$limit) {
-		$limit=$tsimilar;
-                $sname=$tname;
-                $sdom=$tdom;
-                $scrsid=$tcrsid;
-                $sessay=$oldessays{$tkey};
-            }
-        } 
+	if ($tsimilar>$limit) {
+	    $limit=$tsimilar;
+	    $sname=$tname;
+	    $sdom=$tdom;
+	    $scrsid=$tcrsid;
+	    $sessay=$old_essays->{$tkey};
+	}
     }
     if ($limit>0.6) {
        return ($sname,$sdom,$scrsid,$sessay,$limit);
@@ -618,7 +757,7 @@ sub verifyreceipt {
 	    if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
 		$contents.='<tr bgcolor="#ffffe6"><td>&nbsp;'."\n".
 		    '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
-		    '\')"; TARGET=_self>'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
+		    '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
 		    '<td>&nbsp;'.$uname.'&nbsp;</td>'.
 		    '<td>&nbsp;'.$udom.'&nbsp;</td>';
 		if ($receiptparts) {
@@ -661,8 +800,8 @@ sub listStudents {
     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
+    my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
     my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
-
     my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
     $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
 	&Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
@@ -722,10 +861,9 @@ LISTJAVASCRIPT
     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
 	$gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n";
     }
-
-    my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'};
+    my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
+    my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
     $env{'form.Status'} = $saveStatus;
-
     $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n".
 	'<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n".
 	'<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n".
@@ -736,18 +874,17 @@ LISTJAVASCRIPT
         '<option value=".25">Quarter Points</option>'.
         '<option value=".1">Tenths of a Point</option>'.
         '</select>'.
-
-	'<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".
+        &build_section_inputs().
 	'<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
 	'<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
 	'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
 	'<input type="hidden" name="saveState"   value="'.$env{'form.saveState'}.'" />'."\n".
 	'<input type="hidden" name="probTitle"   value="'.$env{'form.probTitle'}.'" />'."\n".
-	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
 
     if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
-	$gradeTable.='<input type="hidden" name="Status"   value="'.$env{'form.Status'}.'" />'."\n";
+	$gradeTable.='<input type="hidden" name="Status"   value="'.$stu_status.'" />'."\n";
     } else {
 	$gradeTable.='<b>Student Status:</b> '.
 	    &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';
@@ -764,7 +901,7 @@ LISTJAVASCRIPT
 	'value="Next->" /> <br />'."\n";
     $gradeTable.=&check_buttons();
     $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />Check For Plagiarism</label>';
-    my ($classlist, undef, $fullname) = &getclasslist($getsec,'1');
+    my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
     $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.
 	'<table border="0"><tr bgcolor="#e6ffff">';
     my $loop = 0;
@@ -839,7 +976,7 @@ LISTJAVASCRIPT
 
 	$ctr++;
 	my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
-
+        my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
 	if ( $perm{'vgr'} eq 'F' ) {
 	    $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);
 	    $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
@@ -847,7 +984,7 @@ LISTJAVASCRIPT
                $student.':'.$$fullname{$student}.':::SECTION'.$section.
 	       ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
 	       &nameUserString(undef,$$fullname{$student},$uname,$udom).
-	       '&nbsp;'.$section.'</td>'."\n";
+	       '&nbsp;'.$section.'/'.$group.'</td>'."\n";
 
 	    if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
 		foreach (sort keys(%status)) {
@@ -1663,15 +1800,26 @@ sub download_all_link {
     return
 }
 
+sub build_section_inputs {
+    my $section_inputs;
+    if ($env{'form.section'} eq '') {
+        $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
+    } else {
+        my @sections = &Apache::loncommon::get_env_multiple('form.section');
+        foreach my $section (@sections) {
+            $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
+        }
+    }
+    return $section_inputs;
+}
+
 # --------------------------- show submissions of a student, option to grade 
 sub submission {
     my ($request,$counter,$total) = @_;
-
     my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
     $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
     $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
-
     my $symb = &get_symb($request); 
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
 
@@ -1691,6 +1839,7 @@ 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);
@@ -1723,7 +1872,7 @@ sub submission {
 	    &Apache::lonxml::clear_problem_counter();
 	    $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
 	}
-	
+
 	# kwclr is the only variable that is guaranteed to be non blank 
         # if this subroutine has been called once.
 	my %keyhash = ();
@@ -1742,22 +1891,22 @@ sub submission {
 	    $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
 	}
 	my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
-
+	my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
 	$request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
 			'<input type="hidden" name="command"    value="handgrade" />'."\n".
 			'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
-			'<input type="hidden" name="Status"     value="'.$env{'form.Status'}.'" />'."\n".
+			'<input type="hidden" name="Status"     value="'.$stu_status.'" />'."\n".
 			'<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
 			'<input type="hidden" name="probTitle"  value="'.$env{'form.probTitle'}.'" />'."\n".
 			'<input type="hidden" name="refresh"    value="off" />'."\n".
 			'<input type="hidden" name="studentNo"  value="" />'."\n".
 			'<input type="hidden" name="gradeOpt"   value="" />'."\n".
-			'<input type="hidden" name="symb"       value="'.$symb.'" />'."\n".
+			'<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 			'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".
 			'<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
 			'<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
 			'<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
-			'<input type="hidden" name="section"    value="'.$env{'form.section'}.'" />'."\n".
+			&build_section_inputs().
 			'<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
 			'<input type="hidden" name="handgrade"  value="'.$env{'form.handgrade'}.'" />'."\n".
 			'<input type="hidden" name="NCT"'.
@@ -1793,10 +1942,10 @@ sub submission {
 #
 	    $request->print(<<KEYWORDS);
 &nbsp;<b>Keyword Options:</b>&nbsp;
-<a href="javascript:keywords(document.SCORE)"; TARGET=_self>List</a>&nbsp; &nbsp;
+<a href="javascript:keywords(document.SCORE);" target="_self">List</a>&nbsp; &nbsp;
 <a href="#" onMouseDown="javascript:getSel(); return false"
  CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
-<a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br />
+<a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br />
 KEYWORDS
 #
 # Load the other essays for similarity check
@@ -1805,12 +1954,17 @@ KEYWORDS
 	    my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
 	    $apath=&escape($apath);
 	    $apath=~s/\W/\_/gs;
-	    %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
+	    %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
         }
     }
 
+# This is where output for one specific student would start
+    my $bgcolor='#DDEEDD';
+    if ($counter%2) { $bgcolor='#DDDDEE'; }
+    $request->print("\n\n".
+                    '<p><table border="2"><tr><th bgcolor="'.$bgcolor.'">'.$env{'form.fullname'}.'</th></tr><tr><td bgcolor="'.$bgcolor.'">');
+
     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
-	$request->print('<br /><br /><br />') if ($counter > 0);
 	my $mode;
 	if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
 	    $mode='both';
@@ -1836,60 +1990,13 @@ KEYWORDS
 	'" value="'.$env{'form.fullname'}.'" />'."\n";
 
     # If any part of the problem is an essay-response (handgraded), then check for collaborators
-    my @col_fullnames;
-    my ($classlist,$fullname);
+    my $fullname;
+    my $col_fullnames = [];
     if ($env{'form.handgrade'} eq 'yes') {
-	($classlist,undef,$fullname) = &getclasslist('all','0');
-	for (keys (%$handgrade)) {
-	    my $ncol = &Apache::lonnet::EXT('resource.'.$_.
-					    '.maxcollaborators',
-                                            $symb,$udom,$uname);
-	    next if ($ncol <= 0);
-            s/\_/\./g;
-            next if ($record{'resource.'.$_.'.collaborators'} eq '');
-            my @goodcollaborators = ();
-            my @badcollaborators  = ();
-	    foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) { 
-		$_ =~ s/[\$\^\(\)]//g;
-		next if ($_ eq '');
-		my ($co_name,$co_dom) = split /\@|:/,$_;
-		$co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
-		next if ($co_name eq $uname && $co_dom eq $udom);
-		# Doing this grep allows 'fuzzy' specification
-		my @Matches = grep /^$co_name:$co_dom$/i,keys %$classlist;
-		if (! scalar(@Matches)) {
-		    push @badcollaborators,$_;
-		} else {
-		    push @goodcollaborators, @Matches;
-		}
-	    }
-            if (scalar(@goodcollaborators) != 0) {
-                $result.='<b>Collaborators: </b>';
-                foreach (@goodcollaborators) {
-		    my ($lastname,$givenn) = split(/,/,$$fullname{$_});
-		    push @col_fullnames, $givenn.' '.$lastname;
-		    $result.=$$fullname{$_}.'&nbsp; &nbsp; &nbsp;';
-		}
-                $result.='<br />'."\n";
-		my ($part)=split(/\./,$_);
-		$result.='<input type="hidden" name="collaborator'.$counter.
-		    '" value="'.$part.':'.(join ':',@goodcollaborators).'" />'.
-		    "\n";
-	    }
-	    if (scalar(@badcollaborators) > 0) {
-		$result.='<table border="0"><tr bgcolor="#ffbbbb"><td>';
-		$result.='This student has submitted ';
-		$result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators';
-		$result .= ': '.join(', ',@badcollaborators);
-		$result .= '</td></tr></table>';
-	    }         
-	    if (scalar(@badcollaborators > $ncol)) {
-		$result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>';
-		$result .= 'This student has submitted too many '.
-		    'collaborators.  Maximum is '.$ncol.'.';
-		$result .= '</td></tr></table>';
-	    }
-	}
+	(my $sub_result,$fullname,$col_fullnames)=
+	    &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
+				 $counter);
+	$result.=$sub_result;
     }
     $request->print($result."\n");
 
@@ -1922,7 +2029,7 @@ KEYWORDS
 			' <b>Collaborative submission by:</b> '.
 			'<a href="javascript:viewSubmitter(\''.
 			$env{"form.$uname:$udom:$partid:submitted_by"}.
-			'\')"; TARGET=_self>'.
+			'\');" target="_self">'.
 			$$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
 		    $request->print($submitby);
 		    next;
@@ -1943,12 +2050,21 @@ KEYWORDS
 		    my $similar='';
 		    if($env{'form.checkPlag'}){
 			my ($oname,$odom,$ocrsid,$oessay,$osim)=
-			    &most_similar($uname,$udom,$subval);
+			    &most_similar($uname,$udom,$subval,\%old_essays);
 			if ($osim) {
 			    $osim=int($osim*100.0);
-			    $similar="<hr /><h3><span class=\"LC_warning\">Essay".
-				" is $osim% similar to an essay by ".
-				&Apache::loncommon::plainname($oname,$odom).
+			    my %old_course_desc = 
+				&Apache::lonnet::coursedescription($ocrsid,
+								   {'one_time' => 1});
+
+			    $similar="<hr /><h3><span class=\"LC_warning\">".
+				&mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])',
+				    $osim,
+				    &Apache::loncommon::plainname($oname,$odom),
+				    $oname,$odom,
+				    $old_course_desc{'description'},
+				    $old_course_desc{'num'},
+				    $old_course_desc{'domain'}).
 				'</span></h3><blockquote><i>'.
 				&keywords_highlight($oessay).
 				'</i></blockquote><hr />';
@@ -2000,7 +2116,7 @@ KEYWORDS
     if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
 	my $toGrade.='<input type="button" value="Grade Student" '.
 	    'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
-	    .$counter.'\');" TARGET=_self> &nbsp;'."\n" if (&canmodify($usec));
+	    .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
 	$toGrade.='</td></tr></table></td></tr></table>'."\n";
 	if (($env{'form.command'} eq 'submission') || 
 	    ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
@@ -2016,16 +2132,16 @@ KEYWORDS
     if ($env{'form.handgrade'} eq 'yes') {
 	my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
 	my $msgfor = $givenn.' '.$lastname;
-	if (scalar(@col_fullnames) > 0) {
-	    my $lastone = pop @col_fullnames;
-	    $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.';
+	if (scalar(@$col_fullnames) > 0) {
+	    my $lastone = pop(@$col_fullnames);
+	    $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
 	}
 	$msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
 	$result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
 	    '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
 	$result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
-	    ',\''.$msgfor.'\')"; TARGET=_self>'.
-	    &mt('Compose message to student').(scalar(@col_fullnames) >= 1 ? 's' : '').'</a><label> ('.
+	    ',\''.$msgfor.'\');" target="_self">'.
+	    &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').'</a><label> ('.
 	    &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
 	    '<img src="'.$request->dir_config('lonIconsURL').
 	    '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
@@ -2071,12 +2187,17 @@ KEYWORDS
     }
     $request->print($result.'</td></tr></table></td></tr></table>'."\n");
 
+# Done with printing info for one student
+
+    $request->print('</td></tr></table></p>');
+
+
     # print end of form
     if ($counter == $total) {
 	my $endform='<table border="0"><tr><td>'."\n";
 	$endform.='<input type="button" value="Save & Next" '.
 	    'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
-	    $total.','.scalar(@partlist).');" TARGET=_self> &nbsp;'."\n";
+	    $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
 	my $ntstu ='<select name="NTSTU">'.
 	    '<option>1</option><option>2</option>'.
 	    '<option>3</option><option>5</option>'.
@@ -2085,9 +2206,9 @@ KEYWORDS
 	$ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
 	$endform.=$ntstu.'student(s) &nbsp;&nbsp;';
 	$endform.='<input type="button" value="Previous" '.
-	    'onClick="javascript:checksubmit(this.form,\'Previous\');" TARGET=_self> &nbsp;'."\n".
+	    'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
 	    '<input type="button" value="Next" '.
-	    'onClick="javascript:checksubmit(this.form,\'Next\');" TARGET=_self> &nbsp;';
+	    'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
 	$endform.='(Next and Previous (student) do not save the scores.)'."\n" ;
         $endform.="<input type='hidden' value='".&get_increment().
             "' name='increment' />";
@@ -2098,6 +2219,62 @@ KEYWORDS
     return '';
 }
 
+sub check_collaborators {
+    my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
+    my ($result,@col_fullnames);
+    my ($classlist,undef,$fullname) = &getclasslist('all','0');
+    foreach my $part (keys(%$handgrade)) {
+	my $ncol = &Apache::lonnet::EXT('resource.'.$part.
+					'.maxcollaborators',
+					$symb,$udom,$uname);
+	next if ($ncol <= 0);
+	$part =~ s/\_/\./g;
+	next if ($record->{'resource.'.$part.'.collaborators'} eq '');
+	my (@good_collaborators, @bad_collaborators);
+	foreach my $possible_collaborator
+	    (split(/,?\s+/,$record->{'resource.'.$part.'.collaborators'})) { 
+	    $possible_collaborator =~ s/[\$\^\(\)]//g;
+	    next if ($possible_collaborator eq '');
+	    my ($co_name,$co_dom) = split(/\@|:/,$possible_collaborator);
+	    $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
+	    next if ($co_name eq $uname && $co_dom eq $udom);
+	    # Doing this grep allows 'fuzzy' specification
+	    my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i, 
+			       keys(%$classlist));
+	    if (! scalar(@matches)) {
+		push(@bad_collaborators, $possible_collaborator);
+	    } else {
+		push(@good_collaborators, @matches);
+	    }
+	}
+	if (scalar(@good_collaborators) != 0) {
+	    $result.='<b>Collaborators: </b>';
+	    foreach my $name (@good_collaborators) {
+		my ($lastname,$givenn) = split(/,/,$$fullname{$name});
+		push(@col_fullnames, $givenn.' '.$lastname);
+		$result.=$fullname->{$name}.'&nbsp; &nbsp; &nbsp;';
+	    }
+	    $result.='<br />'."\n";
+	    my ($part)=split(/\./,$_);
+	    $result.='<input type="hidden" name="collaborator'.$counter.
+		'" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
+		"\n";
+	}
+	if (scalar(@bad_collaborators) > 0) {
+	    $result.='<div class="LC_error">';
+	    $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
+	    $result .= '</div>';
+	}         
+	if (scalar(@bad_collaborators > $ncol)) {
+	    $result .= '<div class="LC_error">';
+	    $result .= &mt('This student has submitted too many '.
+		'collaborators.  Maximum is [_1].',$ncol);
+	    $result .= '</div>';
+	}
+    }
+    return ($result,$fullname,\@col_fullnames);
+}
+
 #--- Retrieve the last submission for all the parts
 sub get_last_submission {
     my ($returnhash)=@_;
@@ -2169,18 +2346,10 @@ sub processHandGrade {
 	    }
 	    my $includemsg = $env{'form.includemsg'.$ctr};
 	    my ($subject,$message,$msgstatus) = ('','','');
-            my $restitle = &Apache::lonnet::gettitle($symb);
-            my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
-                                                $symb,$udom,$uname);
-            my ($feedurl,$baseurl,$showsymb,$messagetail);
-            $feedurl = &Apache::lonnet::clutter($url);
-            if ($encrypturl =~ /^yes$/i) {
-                $baseurl = &Apache::lonenc::encrypted($feedurl,1);
-                $showsymb = &Apache::lonenc::encrypted($symb,1);
-            } else {
-                $baseurl = $feedurl;
-                $showsymb = $symb;
-            }
+	    my $restitle = &Apache::lonnet::gettitle($symb);
+            my ($feedurl,$showsymb) =
+		&get_feedurl_and_symb($symb,$uname,$udom);
+	    my $messagetail;
 	    if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
 		$subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
 		unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
@@ -2193,12 +2362,12 @@ sub processHandGrade {
 		if ($env{'form.withgrades'.$ctr}) {
 		    $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
 		    $messagetail = " for <a href=\"".
-		                   $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
+		                   $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
 		}
 		$msgstatus = 
                     &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
 						     $message.$messagetail,
-                                                     undef,$baseurl,undef,
+                                                     undef,$feedurl,undef,
                                                      undef,undef,$showsymb,
                                                      $restitle);
 		$request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.
@@ -2215,26 +2384,16 @@ sub processHandGrade {
 			if ($errorflag eq 'not_allowed') {
 			    $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
 			    next;
-			} else {
-			    if ($message ne '') {
-                                $encrypturl=
-                                  &Apache::lonnet::EXT('resource.0.encrypturl',
-                                                       $symb,$udom,$collaborator);
-                                if ($encrypturl =~ /^yes$/i) {
-                                    $baseurl = &Apache::lonenc::encrypted($feedurl,1);
-                                    $showsymb = &Apache::lonenc::encrypted($symb,1);
-                                } else {
-                                    $baseurl = $feedurl;
-                                    $showsymb = $symb;
-                                }
-                                if ($env{'form.withgrades'.$ctr}) {
-                                    $messagetail = " for <a href=\"".
+			} elsif ($message ne '') {
+			    my ($baseurl,$showsymb) = 
+				&get_feedurl_and_symb($symb,$collaborator,
+						      $udom);
+			    if ($env{'form.withgrades'.$ctr}) {
+				$messagetail = " for <a href=\"".
                                     $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
-
-                                }
-				$msgstatus = 
-                                    &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
 			    }
+			    $msgstatus = 
+				&Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
 			}
 		    }
 		}
@@ -2588,28 +2747,31 @@ sub handback_files {
 		$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 $url = (&Apache::lonnet::decode_symb($symb))[2];
-		my $feedurl = &Apache::lonnet::clutter($url);
-                my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
-                                                    $symb,$domain,$stuname);
-                my ($baseurl,$showsymb);
-                if ($encrypturl =~ /^yes$/i) {
-                    $baseurl = &Apache::lonenc::encrypted($feedurl,1);
-                    $showsymb = &Apache::lonenc::encrypted($symb,1);
-                } else {
-                    $baseurl = $feedurl;
-                    $showsymb = $symb;
-                }
+		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,
-                         $baseurl,undef,undef,undef,$showsymb,$restitle);
+                         $feedurl,undef,undef,undef,$showsymb,$restitle);
             }
         }
     return;
 }
 
+sub get_feedurl_and_symb {
+    my ($symb,$uname,$udom) = @_;
+    my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
+    $url = &Apache::lonnet::clutter($url);
+    my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
+					$symb,$udom,$uname);
+    if ($encrypturl =~ /^yes$/i) {
+	&Apache::lonenc::encrypted(\$url,1);
+	&Apache::lonenc::encrypted(\$symb,1);
+    }
+    return ($url,$symb);
+}
+
 sub get_submitted_files {
     my ($udom,$uname,$partid,$respid,$record) = @_;
     my @files;
@@ -2748,7 +2910,6 @@ sub version_selected_portfile {
     my $new_answer;
     $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
     if($env{'form.copy'} eq '-1') {
-        &Apache::lonnet::logthis('problem getting file '.$file_name);
         $new_answer = 'problem getting file';
     } else {
         $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
@@ -2969,23 +3130,25 @@ sub viewgrades {
     $result.=&jscriptNform($symb);
 
     #beginning of class grading form
+    my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
-	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="command" value="editgrades" />'."\n".
-	'<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n".
+	&build_section_inputs().
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
-	'<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n".
+	'<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 
     my $sectionClass;
+    my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     if ($env{'form.section'} eq 'all') {
 	$sectionClass='Class </h3>';
     } elsif ($env{'form.section'} eq 'none') {
-	$sectionClass='Students in no Section </h3>';
+	$sectionClass=&mt('Students in no Section').'</h3>';
     } else {
-	$sectionClass='Students in Section '.$env{'form.section'}.'</h3>';
+	$sectionClass=&mt('Students in Section(s) [_1]',$section_display).'</h3>';
     }
-    $result.='<h3>Assign Common Grade To '.$sectionClass;
+    $result.='<h3>'.&mt('Assign Common Grade To [_1]',$sectionClass);
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
 	'<table border=0><tr bgcolor="#ffffdd"><td>';
     #radio buttons/text box for assigning points for a section or class.
@@ -3037,7 +3200,7 @@ sub viewgrades {
     $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n".
 	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
     $result.='<input type="button" value="Revert to Default" '.
-	'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self>';
+	'onClick="javascript:resetEntry('.$ctsparts.');" target="_self" />';
 
     #table listing all the students in a section/class
     #header of table
@@ -3088,11 +3251,15 @@ sub viewgrades {
     $result.='</table></td></tr></table>';
     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
     $result.='<input type="button" value="Save" '.
-	'onClick="javascript:submit();" TARGET=_self /></form>'."\n";
+	'onClick="javascript:submit();" target="_self" /></form>'."\n";
     if (scalar(%$fullname) eq 0) {
 	my $colspan=3+scalar(@parts);
-	$result='<span class="LC_warning">There are no students in section "'.$env{'form.section'}.
-	    '" with enrollment status "'.$env{'form.Status'}.'" to modify or grade.</span>';
+	my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
+        my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
+	$result='<span class="LC_warning">'.
+	    &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade',
+	        $section_display, $stu_status).
+	    '</span>';
     }
     $result.=&show_grading_menu_form($symb);
     return $result;
@@ -3108,7 +3275,7 @@ sub viewstudentgrade {
 	'<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> '.
+	'\');" target="_self">'.$fullname.'</a> '.
 	'<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
     $student=~s/:/_/; # colon doen't work in javascript for names
     foreach my $apart (@$parts) {
@@ -3169,9 +3336,10 @@ sub editgrades {
     my ($request) = @_;
 
     my $symb=&get_symb($request);
-    my $title='<h3><span class="LC_info">Current Grade Status</span></h3>';
-    $title.='<h4><b>Current Resource: </b>'.$env{'form.probTitle'}.'</h4><br />'."\n";
-    $title.='<h4><b>Section: </b>'.$env{'form.section'}.'</h4>'."\n";
+    my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
+    my $title='<h3><span class="LC_info">'.&mt('Current Grade Status').'</span></h3>';
+    $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4><br />'."\n";
+    $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
 
     my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";
     $result.= '<table border="0"><tr bgcolor="#deffff">'.
@@ -3369,7 +3537,7 @@ sub split_part_type {
     my ($partstr) = @_;
     my ($temp,@allparts)=split(/_/,$partstr);
     my $type=pop(@allparts);
-    my $part=join('.',@allparts);
+    my $part=join('_',@allparts);
     return ($part,$type);
 }
 
@@ -3472,6 +3640,7 @@ sub csvuploadmap_header {
     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
     my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
     my $ignore=&mt('Ignore First Line');
+    $symb = &Apache::lonenc::check_encrypt($symb);
     $request->print(<<ENDPICK);
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <h3><span class="LC_info">Uploading Class Grades</span></h3>
@@ -3566,6 +3735,7 @@ sub upcsvScores_form {
     my $upload=&mt("Upload Scores");
     my $upfile_select=&Apache::loncommon::upfile_select_html();
     my $ignore=&mt('Ignore First Line');
+    $symb = &Apache::lonenc::check_encrypt($symb);
     $result.=<<ENDUPFORM;
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <input type="hidden" name="symb" value="$symb" />
@@ -3751,7 +3921,8 @@ sub csvuploadassign {
                 if ($wgt) {
                     $entries{$fields{$dest}}=~s/\s//g;
                     my $pcr=$entries{$fields{$dest}} / $wgt;
-                    my $award='correct_by_override';
+                    my $award=($pcr == 0) ? 'incorrect_by_override'
+                                          : 'correct_by_override';
                     $grades{"resource.$part.awarded"}=$pcr;
                     $grades{"resource.$part.solved"}=$award;
                     $points{$part}=1;
@@ -3773,7 +3944,6 @@ sub csvuploadassign {
 	}
 	if (! %grades) { push(@skipped,"$username:$domain no data to save"); }
 	$grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
-#	&Apache::lonnet::logthis(" storing ".(join('-',%grades)));
 	my $result=&Apache::lonnet::cstore(\%grades,$symb,
 					   $env{'request.course.id'},
 					   $domain,$username);
@@ -3840,7 +4010,7 @@ LISTJAVASCRIPT
 
     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
     $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";
-    my ($titles,$symbx) = &getSymbMap($request);
+    my ($titles,$symbx) = &getSymbMap();
     my ($curpage) =&Apache::lonnet::decode_symb($symb); 
 #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
 #    my $type=($curpage =~ /\.(page|sequence)/);
@@ -3870,11 +4040,12 @@ LISTJAVASCRIPT
 	'<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n".
 	'<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> by dates and submissions</label>'."\n".
 	'<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n";
-
-    $result.='<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".
-	'<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
+    
+    $result.=&build_section_inputs();
+    my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
+    $result.='<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
 	'<input type="hidden" name="command" value="displayPage" />'."\n".
-	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
 
     $result.='&nbsp;<b>'.&mt('Use CODE:').' </b>'.
@@ -3922,7 +4093,6 @@ LISTJAVASCRIPT
 }
 
 sub getSymbMap {
-    my ($request) = @_;
     my $navmap = Apache::lonnavmaps::navmap->new();
 
     my %symbx = ();
@@ -3998,7 +4168,7 @@ sub displayPage {
 	'<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
 	'<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
 	'<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
-	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="overRideScore" value="no" />'."\n".
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
 
@@ -4126,7 +4296,6 @@ sub displaySubByDates {
 
 	my $where = ($isTask ? "$version:resource.$interaction"
 		             : "$version:resource");
-	#&Apache::lonnet::logthis(" got $where");
 	$studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';
 	if ($isCODE) {
 	    $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
@@ -4146,7 +4315,6 @@ sub displaySubByDates {
 
 		    my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
 				               : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
-		    #&Apache::lonnet::logthis("match $matchKey $responseId (".$$record{$version.':'.$matchKey});
 		    $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';
 		    $displaySub[0].='<span class="LC_internal_info">(ID&nbsp;'.
 			$responseId.')</span>&nbsp;<b>';
@@ -4367,18 +4535,88 @@ sub updateGradeByPage {
 #
 #------ start of section for handling grading by page/sequence ---------
 
+=pod
+
+=head1 Bubble sheet grading routines
+
+  For this documentation:
+
+   'scanline' refers to the full line of characters
+   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
+
+
+The overall process is that a scanned in bubble sheet 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
+one of the predefined configurations for what each scanline looks
+like.
+
+Next each scanline is checked for any errors of either 'missing
+bubbles' (it's an error because it may have been mis-scanned
+because too light bubbling), 'double bubble' (each bubble line should
+have no more that one letter picked), invalid or duplicated CODE,
+invalid student ID
+
+If the CODE option is used that determines the randomization of the
+homework problems, either way the student ID is looked up into a
+username:domain.
+
+During the validation phase the instructor can choose to skip scanlines. 
+
+After the validation phase, there are now 3 bubble sheet 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
+file (see &scantron_getfile() for more information)
+
+After all scanlines are either valid, marked as valid or skipped, then
+foreach line foreach problem in the picked sequence, an ssi request is
+made that simulates a user submitting their selected letter(s) against
+the homework problem.
+
+=over 4
+
+
+
+=item defaultFormData
+
+  Returns html hidden inputs used to hold context/default values.
+
+ Arguments:
+  $symb - $symb of the current resource 
+
+=cut
+
 sub defaultFormData {
     my ($symb)=@_;
-    return '
-      <input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+    return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
      '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
      '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 }
 
+
+=pod 
+
+=item getSequenceDropDown
+
+   Return html dropdown of possible sequences to grade
+ 
+ Arguments:
+   $symb - $symb of the current resource 
+
+=cut
+
 sub getSequenceDropDown {
-    my ($request,$symb)=@_;
+    my ($symb)=@_;
     my $result='<select name="selectpage">'."\n";
-    my ($titles,$symbx) = &getSymbMap($request);
+    my ($titles,$symbx) = &getSymbMap();
     my ($curpage)=&Apache::lonnet::decode_symb($symb); 
     my $ctr=0;
     foreach (@$titles) {
@@ -4392,6 +4630,15 @@ sub getSequenceDropDown {
     return $result;
 }
 
+
+=pod 
+
+=item scantron_filenames
+
+   Returns a list of the scantron files in the current course 
+
+=cut
+
 sub scantron_filenames {
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
@@ -4407,6 +4654,17 @@ sub scantron_filenames {
     return @possiblenames;
 }
 
+=pod 
+
+=item scantron_uploads
+
+   Returns  html drop-down list of scantron files in current course.
+
+ Arguments:
+   $file2grade - filename to set as selected in the dropdown
+
+=cut
+
 sub scantron_uploads {
     my ($file2grade) = @_;
     my $result=	'<select name="scantron_selectfile">';
@@ -4418,6 +4676,15 @@ sub scantron_uploads {
     return $result;
 }
 
+=pod 
+
+=item scantron_scantab
+
+  Returns html drop down of the scantron formats in the scantronformat.tab
+  file.
+
+=cut
+
 sub scantron_scantab {
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
     my $result='<select name="scantron_format">'."\n";
@@ -4432,6 +4699,15 @@ sub scantron_scantab {
     return $result;
 }
 
+=pod 
+
+=item scantron_CODElist
+
+  Returns html drop down of the saved CODE lists from current course,
+  generated from earlier printings.
+
+=cut
+
 sub scantron_CODElist {
     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
@@ -4446,23 +4722,48 @@ sub scantron_CODElist {
     return $namechoice;
 }
 
+=pod 
+
+=item scantron_CODEunique
+
+  Returns the html for "Each CODE to be used once" radio.
+
+=cut
+
 sub scantron_CODEunique {
     my $result='<span style="white-space: nowrap;">
                  <label><input type="radio" name="scantron_CODEunique"
-                        value="yes" checked="checked" /> Yes </label>
+                        value="yes" checked="checked" />'.&mt('Yes').' </label>
                 </span>
                 <span style="white-space: nowrap;">
                  <label><input type="radio" name="scantron_CODEunique"
-                        value="no" /> No </label>
+                        value="no" />'.&mt('No').' </label>
                 </span>';
     return $result;
 }
 
+=pod 
+
+=item scantron_selectphase
+
+  Generates the initial screen to start the bubble sheet process.
+  Allows for - starting a grading run.
+             - downloading existing scan data (original, corrected
+                                                or skipped info)
+
+             - uploading new scan data
+
+ Arguments:
+  $r          - The Apache request object
+  $file2grade - name of the file that contain the scanned data to score
+
+=cut
+
 sub scantron_selectphase {
     my ($r,$file2grade) = @_;
     my ($symb)=&get_symb($r);
     if (!$symb) {return '';}
-    my $sequence_selector=&getSequenceDropDown($r,$symb);
+    my $sequence_selector=&getSequenceDropDown($symb);
     my $default_form_data=&defaultFormData($symb);
     my $grading_menu_button=&show_grading_menu_form($symb);
     my $file_selector=&scantron_uploads($file2grade);
@@ -4470,8 +4771,9 @@ sub scantron_selectphase {
     my $CODE_selector=&scantron_CODElist();
     my $CODE_unique=&scantron_CODEunique();
     my $result;
-    #FIXME allow instructor to be able to download the scantron file
-    # and to upload it,
+
+    # Chunk of form to prompt for a file to grade and how:
+
     $result.= <<SCANTRONFORM;
     <table width="100%" border="0">
     <tr>
@@ -4504,7 +4806,7 @@ sub scantron_selectphase {
 	    <td> Options: </td>
             <td>
 	       <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records</label> <br />
-               <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all exisiting corrections</label> <br />
+               <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all existing corrections</label> <br />
                <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label>
 	    </td>
           </tr>
@@ -4524,6 +4826,8 @@ SCANTRONFORM
     if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
         &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
 
+	# Chunk of form to prompt for a scantron file upload.
+
         $r->print(<<SCANTRONFORM);
     <tr>
       <td bgcolor="#777777">
@@ -4569,6 +4873,10 @@ UPLOAD
     </tr>
 SCANTRONFORM
     }
+
+    # Chunk of the form that prompts to view a scoring office file,
+    # corrected file, skipped records in a file.
+
     $r->print(<<SCANTRONFORM);
     <tr>
       <form action='/adm/grades' name='scantron_download'>
@@ -4595,14 +4903,70 @@ SCANTRONFORM
     </tr>
 SCANTRONFORM
 
-    $r->print(<<SCANTRONFORM);
-  </table>
-$grading_menu_button
-SCANTRONFORM
-
+    $r->print('<tr><td bgcolor="#777777">');
+    &Apache::lonpickcode::code_list($r,2);
+    $r->print('</td></tr></table>');
+    $r->print($grading_menu_button);
     return
 }
 
+=pod
+
+=item get_scantron_config
+
+   Parse and return the scantron configuration line selected as a
+   hash of configuration file fields.
+
+ Arguments:
+    which - the name of the configuration to parse from the file.
+
+
+ Returns:
+            If the named configuration is not in the file, an empty
+            hash is returned.
+    a hash with the fields
+      name         - internal name for the this configuration setup
+      description  - text to display to operator that describes this config
+      CODElocation - if 0 or the string 'none'
+                          - no CODE exists for this config
+                     if -1 || the string 'letter'
+                          - a CODE exists for this config and is
+                            a string of letters
+                     Unsupported value (but planned for future support)
+                          if a positive integer
+                               - The CODE exists as the first n items from
+                                 the question section of the form
+                          if the string 'number'
+                               - The CODE exists for this config and is
+                                 a string of numbers
+      CODEstart   - (only matter if a CODE exists) column in the line where
+                     the CODE starts
+      CODElength  - length of the CODE
+      IDstart     - column where the student ID number starts
+      IDlength    - length of the student ID info
+      Qstart      - column where the information from the bubbled
+                    'questions' start
+      Qlength     - number of columns comprising a single bubble line from
+                    the sheet. (usually either 1 or 10)
+      Qon         - either a single character representing the character used
+                    to signal a bubble was chosen in the positional setup, or
+                    the string 'letter' if the letter of the chosen bubble is
+                    in the final, or 'number' if a number representing the
+                    chosen bubble is in the file (1->A 0->J)
+      Qoff        - the character used to represent that a bubble was
+                    left blank
+      PaperID     - if the scanning process generates a unique number for each
+                    sheet scanned the column that this ID number starts in
+      PaperIDlength - number of columns that comprise the unique ID number
+                      for the sheet of paper
+      FirstName   - column that the first name starts in
+      FirstNameLength - number of columns that the first name spans
+ 
+      LastName    - column that the last name starts in
+      LastNameLength - number of columns that the last name spans
+
+=cut
+
 sub get_scantron_config {
     my ($which) = @_;
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
@@ -4635,6 +4999,25 @@ sub get_scantron_config {
     return %config;
 }
 
+=pod 
+
+=item username_to_idmap
+
+    creates a hash keyed by student id with values of the corresponding
+    student username:domain.
+
+  Arguments:
+
+    $classlist - reference to the class list hash. This is a hash
+                 keyed by student name:domain  whose elements are references
+                 to arrays containing various chunks of information
+                 about the student. (See loncoursedata for more info).
+
+  Returns
+    %idmap - the constructed hash
+
+=cut
+
 sub username_to_idmap {
     my ($classlist)= @_;
     my %idmap;
@@ -4645,8 +5028,50 @@ sub username_to_idmap {
     return %idmap;
 }
 
+=pod
+
+=item scantron_fixup_scanline
+
+   Process a requested correction to a scanline.
+
+  Arguments:
+    $scantron_config   - hash from &get_scantron_config()
+    $scan_data         - hash of correction information 
+                          (see &scantron_getfile())
+    $line              - existing scanline
+    $whichline         - line number of the passed in scanline
+    $field             - type of change to process 
+                         (either 
+                          'ID'     -> correct the student ID number
+                          'CODE'   -> correct the CODE
+                          'answer' -> fixup the submitted answers)
+    
+   $args               - hash of additional info,
+                          - 'ID' 
+                               'newid' -> studentID to use in replacement
+                                          of existing one
+                          - 'CODE' 
+                               'CODE_ignore_dup' - set to true if duplicates
+                                                   should be ignored.
+	                       'CODE' - is new code or 'use_unfound'
+                                        if the existing unfound code should
+                                        be used as is
+                          - 'answer'
+                               'response' - new answer or 'none' if blank
+                               'question' - the bubble line to change
+
+  Returns:
+    $line - the modified scanline
+
+  Side effects: 
+    $scan_data - may be updated
+
+=cut
+
+
 sub scantron_fixup_scanline {
     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
+
     if ($field eq 'ID') {
 	if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
 	    return ($line,1,'New value too large');
@@ -4703,6 +5128,25 @@ sub scantron_fixup_scanline {
     return $line;
 }
 
+=pod
+
+=item scan_data
+
+    Edit or look up  an item in the scan_data hash.
+
+  Arguments:
+    $scan_data  - The hash (see scantron_getfile)
+    $key        - shorthand of the key to edit (actual key is
+                  scantronfilename_key).
+    $data        - New value of the hash entry.
+    $delete      - If true, the entry is removed from the hash.
+
+  Returns:
+    The new value of the hash table field (undefined if deleted).
+
+=cut
+
+
 sub scan_data {
     my ($scan_data,$key,$value,$delete)=@_;
     my $filename=$env{'form.scantron_selectfile'};
@@ -4713,11 +5157,68 @@ sub scan_data {
     return $scan_data->{$filename.'_'.$key};
 }
 
+=pod 
+
+=item scantron_parse_scanline
+
+  Decodes a scanline from the selected scantron file
+
+ Arguments:
+    line             - The text of the scantron file line to process
+    whichline        - Line number
+    scantron_config  - Hash describing the format of the scantron lines.
+    scan_data        - Hash of extra information about the scanline
+                       (see scantron_getfile for more information)
+    just_header      - True if should not process question answers but only
+                       the stuff to the left of the answers.
+ Returns:
+   Hash containing the result of parsing the scanline
+
+   Keys are all proceeded by the string 'scantron.'
+
+       CODE    - the CODE in use for this scanline
+       useCODE - 1 if the CODE is invalid but it usage has been forced
+                 by the operator
+       CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
+                            CODEs were selected, but the usage has been
+                            forced by the operator
+       ID  - student ID
+       PaperID - if used, the ID number printed on the sheet when the 
+                 paper was scanned
+       FirstName - first name from the sheet
+       LastName  - last name from the sheet
+
+     if just_header was not true these key may also exist
+
+       missingerror - a list of bubble ranges that are considered to be answers
+                      to a single question that don't have any bubbles filled in.
+                      Of the form questionnumber:firstbubblenumber:count.
+       doubleerror  - a list of bubble ranges that are considered to be answers
+                      to a single question that have more than one bubble filled in.
+                      Of the form questionnumber::firstbubblenumber:count
+   
+                In the above, count is the number of bubble responses in the
+                input line needed to represent the possible answers to the question.
+                e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
+                per line would have count = 2.
+
+       maxquest     - the number of the last bubble line that was parsed
+
+       (<number> starts at 1)
+       <number>.answer - zero or more letters representing the selected
+                         letters from the scanline for the bubble line 
+                         <number>.
+                         if blank there was either no bubble or there where
+                         multiple bubbles, (consult the keys missingerror and
+                         doubleerror if this is an error condition)
+
+=cut
+
 sub scantron_parse_scanline {
-    my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_;
+    my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
     my %record;
-    my $questions=substr($line,$$scantron_config{'Qstart'}-1);
-    my $data=substr($line,0,$$scantron_config{'Qstart'}-1);
+    my $questions=substr($line,$$scantron_config{'Qstart'}-1);  # Answers
+    my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
     if (!($$scantron_config{'CODElocation'} eq 0 ||
 	  $$scantron_config{'CODElocation'} eq 'none')) {
 	if ($$scantron_config{'CODElocation'} < 0 ||
@@ -4747,72 +5248,167 @@ sub scantron_parse_scanline {
     $record{'scantron.LastName'}=
 	substr($data,$$scantron_config{'LastName'}-1,
 	       $$scantron_config{'LastNamelength'});
-    if ($justHeader) { return \%record; }
+    if ($just_header) { return \%record; }
 
     my @alphabet=('A'..'Z');
     my $questnum=0;
+    my $ansnum  =1;		# Multiple 'answer lines'/question.
+
     while ($questions) {
+	my $answers_needed = $bubble_lines_per_response{$questnum};
+	my $answer_length  = $$scantron_config{'Qlength'} * $answers_needed;
+
+
+
 	$questnum++;
-	my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});
-	substr($questions,0,$$scantron_config{'Qlength'})='';
-	if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }
+	my $currentquest = substr($questions,0,$answer_length);
+	$questions       = substr($questions,0,$answer_length)='';
+	if (length($currentquest) < $answer_length) { next; }
+
+	# Qon letter implies for each slot in currentquest we have:
+	#    ? or * for doubles a letter in A-Z for a bubble and
+        #    about anything else (esp. a value of Qoff for missing
+	#    bubbles.
+
+
 	if ($$scantron_config{'Qon'} eq 'letter') {
-	    if ($currentquest eq '?'
-		|| $currentquest eq '*') {
+
+	    if ($currentquest =~ /\?/
+		|| $currentquest =~ /\*/
+		|| (&occurence_count($currentquest, "[A-Z]") > 1)) {
 		push(@{$record{'scantron.doubleerror'}},$questnum);
-		$record{"scantron.$questnum.answer"}='';
+		for (my $ans = 0; $ans < $answers_needed; $ans++) { 
+		    my $bubble = substr($currentquest, $ans, 1);
+		    if ($bubble =~ /[A-Z]/ ) {
+			$record{"scantron.$ansnum.answer"} = $bubble;
+		    } else {
+			$record{"scantron.$ansnum.answer"}='';
+		    }
+		    $ansnum++;
+		}
+
 	    } elsif (!defined($currentquest)
-		     || $currentquest eq $$scantron_config{'Qoff'}
-		     || $currentquest !~ /^[A-Z]$/) {
-		$record{"scantron.$questnum.answer"}='';
+		     || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest))
+		     || (&occurence_count($currentquest, "[A-Z]") == 0)) {
+		for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
+		    $record{"scantron.$ansnum.answer"}='';
+		    $ansnum++;
+
+		}
 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 		    push(@{$record{"scantron.missingerror"}},$questnum);
+		    $ansnum += $answers_needed;
 		}
+
 	    } else {
-		$record{"scantron.$questnum.answer"}=$currentquest;
+		for (my $ans = 0; $ans < $answers_needed; $ans++) {
+		    $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
+		    $ansnum++;
+		}
 	    }
+
+	# Qon 'number' implies each slot gives a digit that indexes the
+	#    the bubbles filled or Qoff or a non number for unbubbled lines.
+        #    and *? for double bubbles on a line.
+	#    these answers are also stored as letters.
+
 	} elsif ($$scantron_config{'Qon'} eq 'number') {
-	    if ($currentquest eq '?'
-		|| $currentquest eq '*') {
+	    if ($currentquest =~ /\?/
+		|| $currentquest =~ /\*/
+		|| (&occurence_count($currentquest, '\d') > 1)) {
 		push(@{$record{'scantron.doubleerror'}},$questnum);
-		$record{"scantron.$questnum.answer"}='';
+		for (my $ans = 0; $ans < $answers_needed; $ans++) {
+		    my $bubble = substr($currentquest, $ans, 1);
+		    if ($bubble =~ /\d/) {
+			$record{"scantron.$ansnum.answer"} = $alphabet[$bubble];
+		    } else {
+			$record{"scantron.$ansnum.answer"}=' ';
+		    }
+		    $ansnum++;
+		}
+
 	    } elsif (!defined($currentquest)
-		     || $currentquest eq $$scantron_config{'Qoff'} 
-		     || $currentquest !~ /^\d$/) {
-		$record{"scantron.$questnum.answer"}='';
+		     || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest)) 
+		     || (&occurence_count($currentquest, '\d') == 0)) {
+		for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
+		    $record{"scantron.$ansnum.answer"}='';
+		    $ansnum++;
+
+		}
 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 		    push(@{$record{"scantron.missingerror"}},$questnum);
+		    $ansnum += $answers_needed;
 		}
+
 	    } else {
-		# wrap zero back to J
-		if ($currentquest eq '0') {
-		    $record{"scantron.$questnum.answer"}=
-			$alphabet[9];
-		} else {
-		    $record{"scantron.$questnum.answer"}=
-			$alphabet[$currentquest-1];
+		$currentquest = &digits_to_letters($currentquest);
+		for (my $ans =0; $ans < $answers_needed; $ans++) {
+		    $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
+		    $ansnum++;
 		}
 	    }
 	} else {
+
+	    # Otherwise there's a positional notation;
+	    # each bubble line requires Qlength items, and there are filled in
+	    # bubbles for each case where there 'Qon' characters.
+	    #
+
 	    my @array=split($$scantron_config{'Qon'},$currentquest,-1);
-	    if (length($array[0]) eq $$scantron_config{'Qlength'}) {
-		$record{"scantron.$questnum.answer"}='';
+
+	    # If the split only  giveas us one element.. the full length of the
+	    # answser string, no bubbles are filled in:
+
+	    if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
+		for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
+		    $record{"scantron.$ansnum.answer"}='';
+		    $ansnum++;
+
+		}
 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 		    push(@{$record{"scantron.missingerror"}},$questnum);
 		}
-	    } else {
-		$record{"scantron.$questnum.answer"}=
-		    $alphabet[length($array[0])];
+	    } elsif (scalar(@array) lt 2) {
+
+		my $location      = length($array[0]);
+		my $line_num      = $location / $$scantron_config{'Qlength'};
+		my $bubble        = $alphabet[$location % $$scantron_config{'Qlength'}];
+
+		for (my $ans = 0; $ans < $answers_needed; $ans++) {
+		    if ($ans eq $line_num) {
+			$record{"scantron.$ansnum.answer"} = $bubble;
+		    } else {
+			$record{"scantron.$ansnum.answer"} = ' ';
+		    }
+		    $ansnum++;
+		}
 	    }
-	    if (scalar(@array) gt 2) {
+	    #  If there's more than one instance of a bubble character
+	    #  That's a double bubble; with positional notation we can
+	    #  record all the bubbles filled in as well as the 
+	    #  fact this response consists of multiple bubbles.
+	    #
+	    else {
 		push(@{$record{'scantron.doubleerror'}},$questnum);
+
+		my $first_answer = $ansnum;
+		for (my $ans =0; $ans < $answers_needed; $ans++) {
+		    my $item = $first_answer+$ans;
+		    $record{"scantron.$item.answer"} = '';
+		}
+
 		my @ans=@array;
-		my $i=length($ans[0]);shift(@ans);
+		my $i=0;
+		my $increment = 0;
 		while ($#ans) {
-		    $i+=length($ans[0])+1;
-		    $record{"scantron.$questnum.answer"}.=$alphabet[$i];
+		    $i+=length($ans[0]) + $increment;
+		    my $line   = int($i/$$scantron_config{'Qlength'} + $first_answer);
+		    my $bubble = $i%$$scantron_config{'Qlength'};
+		    $record{"scantron.$line.answer"}.=$alphabet[$bubble];
 		    shift(@ans);
+		    $increment = 1;
 		}
+		$ansnum += $answers_needed;
 	    }
 	}
     }
@@ -4820,6 +5416,24 @@ sub scantron_parse_scanline {
     return \%record;
 }
 
+=pod
+
+=item scantron_add_delay
+
+   Adds an error message that occurred during the grading phase to a
+   queue of messages to be shown after grading pass is complete
+
+ Arguments:
+   $delayqueue  - arrary ref of hash ref of error messages
+   $scanline    - the scanline that caused the error
+   $errormesage - the error message
+   $errorcode   - a numeric code for the error
+
+ Side Effects:
+   updates the $delayqueue to have a new hash ref of the error
+
+=cut
+
 sub scantron_add_delay {
     my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
     push(@$delayqueue,
@@ -4828,6 +5442,24 @@ sub scantron_add_delay {
 	 );
 }
 
+=pod
+
+=item scantron_find_student
+
+   Finds the username for the current scanline
+
+  Arguments:
+   $scantron_record - hash result from scantron_parse_scanline
+   $scan_data       - hash of correction information 
+                      (see &scantron_getfile() form more information)
+   $idmap           - hash from &username_to_idmap()
+   $line            - number of current scanline
+ 
+  Returns:
+   Either 'username:domain' or undef if unknown
+
+=cut
+
 sub scantron_find_student {
     my ($scantron_record,$scan_data,$idmap,$line)=@_;
     my $scanID=$$scantron_record{'scantron.ID'};
@@ -4842,6 +5474,15 @@ sub scantron_find_student {
     return undef;
 }
 
+=pod
+
+=item scantron_filter
+
+   Filter sub for lonnavmaps, filters out hidden resources if ignore
+   hidden resources was selected
+
+=cut
+
 sub scantron_filter {
     my ($curres)=@_;
 
@@ -4858,6 +5499,15 @@ sub scantron_filter {
     return 0;
 }
 
+=pod
+
+=item scantron_process_corrections
+
+   Gets correction information out of submitted form data and corrects
+   the scanline
+
+=cut
+
 sub scantron_process_corrections {
     my ($r) = @_;
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
@@ -4915,12 +5565,30 @@ sub scantron_process_corrections {
     }
 }
 
+=pod
+
+=item reset_skipping_status
+
+   Forgets the current set of remember skipped scanlines (and thus
+   reverts back to considering all lines in the
+   scantron_skipped_<filename> file)
+
+=cut
+
 sub reset_skipping_status {
     my ($scanlines,$scan_data)=&scantron_getfile();
     &scan_data($scan_data,'remember_skipping',undef,1);
     &scantron_putfile(undef,$scan_data);
 }
 
+=pod
+
+=item start_skipping
+
+   Marks a scanline to be skipped. 
+
+=cut
+
 sub start_skipping {
     my ($scan_data,$i)=@_;
     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
@@ -4932,6 +5600,14 @@ sub start_skipping {
     &scan_data($scan_data,'remember_skipping',join(':',%remembered));
 }
 
+=pod
+
+=item should_be_skipped
+
+   Checks whether a scanline should be skipped.
+
+=cut
+
 sub should_be_skipped {
     my ($scanlines,$scan_data,$i)=@_;
     if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
@@ -4947,6 +5623,15 @@ sub should_be_skipped {
     return 1;
 }
 
+=pod
+
+=item remember_current_skipped
+
+   Discovers what scanlines are in the scantron_skipped_<filename>
+   file and remembers them into scan_data for later use.
+
+=cut
+
 sub remember_current_skipped {
     my ($scanlines,$scan_data)=&scantron_getfile();
     my %to_remember;
@@ -4960,6 +5645,16 @@ sub remember_current_skipped {
     &scantron_putfile(undef,$scan_data);
 }
 
+=pod
+
+=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
+    something went wrong.
+
+=cut
+
 sub check_for_error {
     my ($r,$result)=@_;
     if ($result ne 'ok' && $result ne 'not_found' ) {
@@ -4967,6 +5662,15 @@ sub check_for_error {
     }
 }
 
+=pod
+
+=item scantron_warning_screen
+
+   Interstitial screen to make sure the operator has selected the
+   correct options before we start the validation phase.
+
+=cut
+
 sub scantron_warning_screen {
     my ($button_text)=@_;
     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
@@ -4999,6 +5703,15 @@ $CODElist
 STUFF
 }
 
+=pod
+
+=item scantron_do_warning
+
+   Check if the operator has picked something for all required
+   fields. Error out if something is missing.
+
+=cut
+
 sub scantron_do_warning {
     my ($r)=@_;
     my ($symb)=&get_symb($r);
@@ -5030,6 +5743,14 @@ STUFF
     return '';
 }
 
+=pod
+
+=item scantron_form_start
+
+    html hidden input for remembering all selected grading options
+
+=cut
+
 sub scantron_form_start {
     my ($max_bubble)=@_;
     my $result= <<SCANTRONFORM;
@@ -5044,9 +5765,31 @@ sub scantron_form_start {
   <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
   <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
 SCANTRONFORM
+
+  my $line = 0;
+    while (defined($env{"form.scantron.bubblelines.$line"})) {
+       my $chunk =
+	   '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
+       $chunk .=
+	   '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
+       $result .= $chunk;
+       $line++;
+   }
     return $result;
 }
 
+=pod
+
+=item scantron_validate_file
+
+    Dispatch routine for doing validation of a bubble sheet data file.
+
+    Also processes any necessary information resets that need to
+    occur before validation begins (ignore previous corrections,
+    restarting the skipped records processing)
+
+=cut
+
 sub scantron_validate_file {
     my ($r) = @_;
     my ($symb)=&get_symb($r);
@@ -5054,7 +5797,7 @@ sub scantron_validate_file {
     my $default_form_data=&defaultFormData($symb);
     
     # do the detection of only doing skipped records first befroe we delete
-    # them  when doing the corrections reset
+    # them when doing the corrections reset
     if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
 	&reset_skipping_status();
     }
@@ -5073,7 +5816,7 @@ sub scantron_validate_file {
     if ($env{'form.scantron_corrections'}) {
 	&scantron_process_corrections($r);
     }
-    $r->print("<p>Gathering neccessary info.</p>");$r->rflush();
+    $r->print("<p>Gathering necessary info.</p>");$r->rflush();
     #get the student pick code ready
     $r->print(&Apache::loncommon::studentbrowser_javascript());
     my $max_bubble=&scantron_get_maxbubble();
@@ -5090,6 +5833,7 @@ sub scantron_validate_file {
     }
     my $currentphase=$env{'form.validatepass'};
 
+
     my $stop=0;
     while (!$stop && $currentphase < scalar(@validate_phases)) {
 	$r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");
@@ -5130,6 +5874,17 @@ STUFF
     return '';
 }
 
+
+=pod
+
+=item scantron_remove_file
+
+   Removes the requested bubble sheet data file, makes sure that
+   scantron_original_<filename> is never removed
+
+
+=cut
+
 sub scantron_remove_file {
     my ($which)=@_;
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
@@ -5144,6 +5899,18 @@ sub scantron_remove_file {
     return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
 }
 
+
+=pod
+
+=item scantron_remove_scan_data
+
+   Removes all scan_data correction for the requested bubble sheet
+   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.)
+
+=cut
+
 sub scantron_remove_scan_data {
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
@@ -5166,6 +5933,51 @@ sub scantron_remove_scan_data {
     return $result;
 }
 
+
+=pod
+
+=item scantron_getfile
+
+    Fetches the requested bubble sheet data file (all 3 versions), and
+    the scan_data hash
+  
+  Arguments:
+    None
+
+  Returns:
+    2 hash references
+
+     - first one has 
+         orig      -
+         corrected -
+         skipped   -  each of which points to an array ref of the specified
+                      file broken up into individual lines
+         count     - number of scanlines
+ 
+     - second is the scan_data hash possible keys are
+       ($number refers to scanline numbered $number and thus the key affects
+        only that scanline
+        $bubline refers to the specific bubble line element and the aspects
+        refers to that specific bubble line element)
+
+       $number.user - username:domain to use
+       $number.CODE_ignore_dup 
+                    - ignore the duplicate CODE error 
+       $number.useCODE
+                    - use the CODE in the scanline as is
+       $number.no_bubble.$bubline
+                    - it is valid that there is no bubbled in bubble
+                      at $number $bubline
+       remember_skipping
+                    - a frozen hash containing keys of $number and values
+                      of either 
+                        1 - we are on a 'do skipped records pass' and plan
+                            on processing this line
+                        2 - we are on a 'do skipped records pass' and this
+                            scanline has been marked to skip yet again
+
+=cut
+
 sub scantron_getfile {
     #FIXME really would prefer a scantron directory
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
@@ -5198,6 +6010,21 @@ sub scantron_getfile {
     return (\%scanlines,\%scan_data);
 }
 
+=pod
+
+=item lonnet_putfile
+
+   Wrapper routine to call &Apache::lonnet::finishuserfileupload
+
+ Arguments:
+   $contents - data to store
+   $filename - filename to store $contents into
+
+ Returns:
+   result value from &Apache::lonnet::finishuserfileupload
+
+=cut
+
 sub lonnet_putfile {
     my ($contents,$filename)=@_;
     my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
@@ -5207,6 +6034,22 @@ sub lonnet_putfile {
 
 }
 
+=pod
+
+=item scantron_putfile
+
+    Stores the current version of the bubble sheet data files, and the
+    scan_data hash. (Does not modify the original version only the
+    corrected and skipped versions.
+
+ Arguments:
+    $scanlines - hash ref that looks like the first return value from
+                 &scantron_getfile()
+    $scan_data - hash ref that looks like the second return value from
+                 &scantron_getfile()
+
+=cut
+
 sub scantron_putfile {
     my ($scanlines,$scan_data) = @_;
     #FIXME really would prefer a scantron directory
@@ -5227,6 +6070,28 @@ sub scantron_putfile {
     &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
 }
 
+=pod
+
+=item scantron_get_line
+
+   Returns the correct version of the scanline
+
+ Arguments:
+    $scanlines - hash ref that looks like the first return value from
+                 &scantron_getfile()
+    $scan_data - hash ref that looks like the second return value from
+                 &scantron_getfile()
+    $i         - number of the requested line (starts at 0)
+
+ Returns:
+   A scanline, (either the original or the corrected one if it
+   exists), or undef if the requested scanline should be
+   skipped. (Either because it's an skipped scanline, or it's an
+   unskipped scanline and we are not doing a 'do skipped scanlines'
+   pass.
+
+=cut
+
 sub scantron_get_line {
     my ($scanlines,$scan_data,$i)=@_;
     if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
@@ -5235,6 +6100,23 @@ sub scantron_get_line {
     return $scanlines->{'orig'}[$i]; 
 }
 
+=pod
+
+=item scantron_todo_count
+
+    Counts the number of scanlines that need processing.
+
+ Arguments:
+    $scanlines - hash ref that looks like the first return value from
+                 &scantron_getfile()
+    $scan_data - hash ref that looks like the second return value from
+                 &scantron_getfile()
+
+ Returns:
+    $count - number of scanlines to process
+
+=cut
+
 sub get_todo_count {
     my ($scanlines,$scan_data)=@_;
     my $count=0;
@@ -5246,6 +6128,25 @@ sub get_todo_count {
     return $count;
 }
 
+=pod
+
+=item scantron_put_line
+
+    Updates the 'corrected' or 'skipped' versions of the bubble sheet
+    data file.
+
+ Arguments:
+    $scanlines - hash ref that looks like the first return value from
+                 &scantron_getfile()
+    $scan_data - hash ref that looks like the second return value from
+                 &scantron_getfile()
+    $i         - line number to update
+    $newline   - contents of the updated scanline
+    $skip      - if true make the line for skipping and update the
+                 'skipped' file
+
+=cut
+
 sub scantron_put_line {
     my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
     if ($skip) {
@@ -5256,6 +6157,21 @@ sub scantron_put_line {
     $scanlines->{'corrected'}[$i]=$newline;
 }
 
+=pod
+
+=item scantron_clear_skip
+
+   Remove a line from the 'skipped' file
+
+ Arguments:
+    $scanlines - hash ref that looks like the first return value from
+                 &scantron_getfile()
+    $scan_data - hash ref that looks like the second return value from
+                 &scantron_getfile()
+    $i         - line number to update
+
+=cut
+
 sub scantron_clear_skip {
     my ($scanlines,$scan_data,$i)=@_;
     if (exists($scanlines->{'skipped'}[$i])) {
@@ -5265,6 +6181,15 @@ sub scantron_clear_skip {
     return 0;
 }
 
+=pod
+
+=item scantron_filter_not_exam
+
+   Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
+   filter out resources that are not marked as 'exam' mode
+
+=cut
+
 sub scantron_filter_not_exam {
     my ($curres)=@_;
     
@@ -5281,6 +6206,15 @@ sub scantron_filter_not_exam {
     return 0;
 }
 
+=pod
+
+=item scantron_validate_sequence
+
+    Validates the selected sequence, checking for resource that are
+    not set to exam mode.
+
+=cut
+
 sub scantron_validate_sequence {
     my ($r,$currentphase) = @_;
 
@@ -5304,6 +6238,15 @@ sub scantron_validate_sequence {
     return (0,$currentphase+1);
 }
 
+=pod
+
+=item scantron_validate_ID
+
+   Validates all scanlines in the selected file to not have any
+   invalid or underspecified student IDs
+
+=cut
+
 sub scantron_validate_ID {
     my ($r,$currentphase) = @_;
     
@@ -5314,6 +6257,8 @@ sub scantron_validate_ID {
     #get scantron line setup
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
+    
+    &scantron_get_maxbubble();	# parse needs the bubble_lines.. array.
 
     my %found=('ids'=>{},'usernames'=>{});
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
@@ -5366,10 +6311,40 @@ sub scantron_validate_ID {
     return (0,$currentphase+1);
 }
 
+=pod
+
+=item scantron_get_correction
+
+   Builds the interface screen to interact with the operator to fix a
+   specific error condition in a specific scanline
+
+ Arguments:
+    $r           - Apache request object
+    $i           - number of the current scanline
+    $scan_record - hash ref as returned from &scantron_parse_scanline()
+    $scan_config - hash ref as returned from &get_scantron_config()
+    $line        - full contents of the current scanline
+    $error       - error condition, valid values are
+                   'incorrectCODE', 'duplicateCODE',
+                   'doublebubble', 'missingbubble',
+                   'duplicateID', 'incorrectID'
+    $arg         - extra information needed
+       For errors:
+         - duplicateID   - paper number that this studentID was seen before on
+         - duplicateCODE - array ref of the paper numbers this CODE was
+                           seen on before
+         - incorrectCODE - current incorrect CODE 
+         - doublebubble  - array ref of the bubble lines that have double
+                           bubble errors
+         - missingbubble - array ref of the bubble lines that have missing
+                           bubble errors
+
+=cut
+
 sub scantron_get_correction {
     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
 
-#FIXME in the case of a duplicated ID the previous line, probaly need
+#FIXME in the case of a duplicated ID the previous line, probably need
 #to show both the current line and the previous one and allow skipping
 #the previous one or the current one
 
@@ -5468,8 +6443,10 @@ ENDSCRIPT
 	$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"};
-	    &scantron_bubble_selector($r,$scan_config,$question,split('',$selected));
+	    my $selected  = &get_response_bubbles($scan_record, $question);
+	    my @select_array = split(/:/,$selected);
+	    &scantron_bubble_selector($r,$scan_config,$question,
+				      @select_array);
 	}
     } elsif ($error eq 'missingbubble') {
 	$r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");
@@ -5479,7 +6456,7 @@ ENDSCRIPT
 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 		  join(',',@{$arg}).'" />');
 	foreach my $question (@{$arg}) {
-	    my $selected=$$scan_record{"scantron.$question.answer"};
+	    my $selected = &get_response_bubbles($scan_record, $question);
 	    &scantron_bubble_selector($r,$scan_config,$question);
 	}
     } else {
@@ -5489,32 +6466,98 @@ ENDSCRIPT
 
 }
 
+=pod
+
+=item scantron_bubble_selector
+  
+   Generates the html radiobuttons to correct a single bubble line
+   possibly showing the existing the selected bubbles if known
+
+ Arguments:
+    $r           - Apache request object
+    $scan_config - hash from &get_scantron_config()
+    $quest       - number of the bubble line to make a corrector for
+    $lines       - array of answer lines.
+
+=cut
+
 sub scantron_bubble_selector {
-    my ($r,$scan_config,$quest,@selected)=@_;
+    my ($r,$scan_config,$quest,@lines)=@_;
     my $max=$$scan_config{'Qlength'};
 
+
     my $scmode=$$scan_config{'Qon'};
+
+    my $bubble_length = scalar(@lines);
+
+
     if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }	     
 
+    my $response = $quest-1;
+    my $lines = $bubble_lines_per_response{$response};
+
+    my $total_lines = $lines*2;
     my @alphabet=('A'..'Z');
-    $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>");
-    for (my $i=0;$i<$max+1;$i++) {
-	$r->print("\n".'<td align="center">');
-	if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
-	else { $r->print('&nbsp;'); }
-	$r->print('</td>');
-    }
-    $r->print('</tr><tr>');
-    for (my $i=0;$i<$max;$i++) {
-	$r->print("\n".
-		  '<td><label><input type="radio" name="scantron_correct_Q_'.
-		  $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
-    }
-    $r->print('<td><label><input type="radio" name="scantron_correct_Q_'.
+    $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");
+
+    for (my $l = 0; $l < $lines; $l++) {
+	if ($l != 0) {
+	    $r->print('<tr>');
+	}
+	my @selected = split(//,$lines[$l]);
+	for (my $i=0;$i<$max;$i++) {
+	    $r->print("\n".'<td align="center">');
+	    if ($selected[0] eq $alphabet[$i]) { 
+		$r->print('X'); 
+		shift(@selected) ;
+	    } else { 
+		$r->print('&nbsp;'); 
+	    }
+	    $r->print('</td>');
+	    
+	}
+
+	if ($l == 0) {
+	    my $lspan = $total_lines * 2;   #  2 table rows per bubble line.
+
+	    $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'.
 	      $quest.'" value="none" /> No bubble </label></td>');
-    $r->print('</tr></table>');
+	
+	}
+
+	$r->print('</tr><tr>');
+
+	# FIXME: This may have to be a bit more clever for
+	#        multiline questions (different values e.g..).
+
+	for (my $i=0;$i<$max;$i++) {
+	    $r->print("\n".
+		      '<td><label><input type="radio" name="scantron_correct_Q_'.
+		      $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
+	}
+	$r->print('</tr>');
+
+	    
+    }
+    $r->print('</table>');
 }
 
+=pod
+
+=item num_matches
+
+   Counts the number of characters that are the same between the two arguments.
+
+ Arguments:
+   $orig - CODE from the scanline
+   $code - CODE to match against
+
+ Returns:
+   $count - integer count of the number of same characters between the
+            two arguments
+
+=cut
+
 sub num_matches {
     my ($orig,$code) = @_;
     my @code=split(//,$code);
@@ -5526,6 +6569,26 @@ sub num_matches {
     return $same;
 }
 
+=pod
+
+=item scantron_get_closely_matching_CODEs
+
+   Cycles through all CODEs and finds the set that has the greatest
+   number of same characters as the provided CODE
+
+ Arguments:
+   $allcodes - hash ref returned by &get_codes()
+   $CODE     - CODE from the current scanline
+
+ Returns:
+   2 element list
+    - first elements is number of how closely matching the best fit is 
+      (5 means best set has 5 matching characters)
+    - second element is an arrary ref containing the set of valid CODEs
+      that best fit the passed in CODE
+
+=cut
+
 sub scantron_get_closely_matching_CODEs {
     my ($allcodes,$CODE)=@_;
     my @CODEs;
@@ -5536,6 +6599,23 @@ sub scantron_get_closely_matching_CODEs
     return ($#CODEs,$CODEs[-1]);
 }
 
+=pod
+
+=item get_codes
+
+   Builds a hash which has keys of all of the valid CODEs from the selected
+   set of remembered CODEs.
+
+ Arguments:
+  $old_name - name of the set of remembered CODEs
+  $cdom     - domain of the course
+  $cnum     - internal course name
+
+ Returns:
+  %allcodes - keys are the valid CODEs, values are all 1
+
+=cut
+
 sub get_codes {
     my ($old_name, $cdom, $cnum) = @_;
     if (!$old_name) {
@@ -5558,6 +6638,16 @@ sub get_codes {
     return %allcodes;
 }
 
+=pod
+
+=item scantron_validate_CODE
+
+   Validates all scanlines in the selected file to not have any
+   invalid or underspecified CODEs and that none of the codes are
+   duplicated if this was requested.
+
+=cut
+
 sub scantron_validate_CODE {
     my ($r,$currentphase) = @_;
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
@@ -5575,6 +6665,8 @@ sub scantron_validate_CODE {
 
     my %allcodes=&get_codes();
 
+    &scantron_get_maxbubble();	# parse needs the lines per response array.
+
     my ($scanlines,$scan_data)=&scantron_getfile();
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
@@ -5609,6 +6701,15 @@ sub scantron_validate_CODE {
     return (0,$currentphase+1);
 }
 
+=pod
+
+=item scantron_validate_doublebubble
+
+   Validates all scanlines in the selected file to not have any
+   bubble lines with multiple bubbles marked.
+
+=cut
+
 sub scantron_validate_doublebubble {
     my ($r,$currentphase) = @_;
     #get student info
@@ -5618,6 +6719,9 @@ sub scantron_validate_doublebubble {
     #get scantron line setup
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
+
+    &scantron_get_maxbubble();	# parse needs the bubble line array.
+
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 	if ($line=~/^[\s\cz]*$/) { next; }
@@ -5632,32 +6736,100 @@ sub scantron_validate_doublebubble {
     return (0,$currentphase+1);
 }
 
+=pod
+
+=item scantron_get_maxbubble
+
+   Returns the maximum number of bubble lines that are expected to
+   occur. Does this by walking the selected sequence rendering the
+   resource and then checking &Apache::lonxml::get_problem_counter()
+   for what the current value of the problem counter is.
+
+   Caches the results to $env{'form.scantron_maxbubble'},
+   $env{'form.scantron.bubble_lines.n'} and 
+   $env{'form.scantron.first_bubble_line.n'}
+   which are the total number of bubble, lines, the number of bubble
+   lines for reponse n and number of the first bubble line for response n.
+
+=cut
+
 sub scantron_get_maxbubble {    
     if (defined($env{'form.scantron_maxbubble'}) &&
 	$env{'form.scantron_maxbubble'}) {
+	&restore_bubble_lines();
 	return $env{'form.scantron_maxbubble'};
     }
 
-    my $navmap=Apache::lonnavmaps::navmap->new();
-    my (undef,undef,$sequence)=
+    my (undef, undef, $sequence) =
 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
 
+    my $navmap=Apache::lonnavmaps::navmap->new();
     my $map=$navmap->getResourceByUrl($sequence);
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 
     &Apache::lonxml::clear_problem_counter();
 
+    my $uname       = $env{'form.student'};
+    my $udom        = $env{'form.userdom'};
+    my $cid         = $env{'request.course.id'};
+    my $total_lines = 0;
+    %bubble_lines_per_response = ();
+    %first_bubble_line         = ();
+
+  
+    my $response_number = 0;
+    my $bubble_line     = 0;
     foreach my $resource (@resources) {
+	my $symb = $resource->symb();
+	&Apache::lonxml::clear_bubble_lines_for_part();
 	my $result=&Apache::lonnet::ssi($resource->src(),
-					('symb' => $resource->symb()));
+					('symb' => $resource->symb()),
+					('grade_target' => 'analyze'),
+					('grade_courseid' => $cid),
+					('grade_domain' => $udom),
+					('grade_username' => $uname));
+	my (undef, $an) =
+	    split(/_HASH_REF__/,$result, 2);
+
+	my %analysis = &Apache::lonnet::str2hash($an);
+
+
+
+	foreach my $part_id (@{$analysis{'parts'}}) {
+
+
+	    my $lines = $analysis{"$part_id.bubble_lines"};;
+
+	    # TODO - make this a persistent hash not an array.
+
+
+	    $first_bubble_line{$response_number}           = $bubble_line;
+	    $bubble_lines_per_response{$response_number}   = $lines;
+	    $response_number++;
+
+	    $bubble_line +=  $lines;
+	    $total_lines +=  $lines;
+	}
+
     }
     &Apache::lonnet::delenv('scantron\.');
-    $env{'form.scantron_maxbubble'} =
-	&Apache::lonxml::get_problem_counter()-1;
 
+    &save_bubble_lines();
+    $env{'form.scantron_maxbubble'} =
+	$total_lines;
     return $env{'form.scantron_maxbubble'};
 }
 
+=pod
+
+=item scantron_validate_missingbubbles
+
+   Validates all scanlines in the selected file to not have any
+    answers that don't have bubbles that have not been verified
+    to be bubble free.
+
+=cut
+
 sub scantron_validate_missingbubbles {
     my ($r,$currentphase) = @_;
     #get student info
@@ -5690,6 +6862,30 @@ sub scantron_validate_missingbubbles {
     return (0,$currentphase+1);
 }
 
+=pod
+
+=item scantron_process_students
+
+   Routine that does the actual grading of the bubble sheet information.
+
+   The parsed scanline hash is added to %env 
+
+   Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
+   foreach resource , with the form data of
+
+	'submitted'     =>'scantron' 
+	'grade_target'  =>'grade',
+	'grade_username'=> username of student
+	'grade_domain'  => domain of student
+	'grade_courseid'=> of course
+	'grade_symb'    => symb of resource to grade
+
+    This triggers a grading pass. The problem grading code takes care
+    of converting the bubbled letter information (now in %env) into a
+    valid submission.
+
+=cut
+
 sub scantron_process_students {
     my ($r) = @_;
     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
@@ -5724,6 +6920,9 @@ SCANTRONFORM
     my $start=&Time::HiRes::time();
     my $i=-1;
     my ($uname,$udom,$started);
+
+    &scantron_get_maxbubble();	# Need the bubble lines array to parse.
+
     while ($i<$scanlines->{'count'}) {
  	($uname,$udom)=('','');
  	$i++;
@@ -5774,8 +6973,6 @@ SCANTRONFORM
 	    }
 	    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; }
 	}
@@ -5794,6 +6991,14 @@ SCANTRONFORM
     return '';
 }
 
+=pod
+
+=item scantron_upload_scantron_data
+
+    Creates the screen for adding a new bubble sheet data file to a course.
+
+=cut
+
 sub scantron_upload_scantron_data {
     my ($r)=@_;
     $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
@@ -5830,6 +7035,15 @@ UPLOAD
     return '';
 }
 
+=pod
+
+=item scantron_upload_scantron_data_save
+
+   Adds a provided bubble information data file to the course if user
+   has the correct privileges to do so.  
+
+=cut
+
 sub scantron_upload_scantron_data_save {
     my($r)=@_;
     my ($symb)=&get_symb($r,1);
@@ -5885,6 +7099,14 @@ sub scantron_upload_scantron_data_save {
     return '';
 }
 
+=pod
+
+=item valid_file
+
+   Validates that the requested bubble data file exists in the course.
+
+=cut
+
 sub valid_file {
     my ($requested_file)=@_;
     foreach my $filename (sort(&scantron_filenames())) {
@@ -5893,6 +7115,16 @@ sub valid_file {
     return 0;
 }
 
+=pod
+
+=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
+   course.
+
+=cut
+
 sub scantron_download_scantron_data {
     my ($r)=@_;
     my $default_form_data=&defaultFormData(&get_symb($r,1));
@@ -5929,6 +7161,12 @@ DOWNLOAD
     return '';
 }
 
+=pod
+
+=back
+
+=cut
+
 #-------- end of section for handling grading scantron forms -------
 #
 #-------------------------------------------------------------------
@@ -5939,7 +7177,7 @@ DOWNLOAD
 sub show_grading_menu_form {
     my ($symb)=@_;
     my $result.='<br /><form action="/adm/grades" method="post">'."\n".
-	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
 	'<input type="hidden" name="command" value="gradingmenu" />'."\n".
 	'<input type="submit" name="submit" value="Grading Menu" />'."\n".
@@ -5959,8 +7197,128 @@ sub savedState {
     return \%savedState;
 }
 
-#--- Displays the main menu page -------
-sub gradingmenu {
+sub grading_menu {
+    my ($request) = @_;
+    my ($symb)=&get_symb($request);
+    if (!$symb) {return '';}
+    my $probTitle = &Apache::lonnet::gettitle($symb);
+    my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
+
+    $request->print($table);
+    my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
+                  'handgrade'=>$hdgrade,
+                  'probTitle'=>$probTitle,
+                  'command'=>'submit_options',
+                  'saveState'=>"",
+                  'gradingMenu'=>1,
+                  'showgrading'=>"yes");
+    my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+    my @menu = ({ url => $url,
+                     name => &mt('Manual Grading/View Submissions'),
+                     short_description => 
+    &mt('Start the process of hand grading submissions.'),
+                 });
+    $fields{'command'} = 'csvform';
+    $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+    push (@menu, { url => $url,
+                   name => &mt('Upload Scores'),
+                   short_description => 
+            &mt('Specify a file containing the class scores for current resource.')});
+    $fields{'command'} = 'processclicker';
+    $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+    push (@menu, { url => $url,
+                   name => &mt('Process Clicker'),
+                   short_description => 
+            &mt('Specify a file containing the clicker information for this resource.')});
+    $fields{'command'} = 'scantron_selectphase';
+    $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+    push (@menu, { url => $url,
+                   name => &mt('Grade/Manage Scantron Forms'),
+                   short_description => 
+            &mt('')});
+    $fields{'command'} = 'verify';
+    $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+    push (@menu, { url => "",
+                   name => &mt('Verify Receipt'),
+                   short_description => 
+            &mt('')});
+    #
+    # Create the menu
+    my $Str;
+    # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>';
+    $Str .= '<form method="post" action="" name="gradingMenu">';
+    $Str .= '<input type="hidden" name="command" value="" />'.
+    	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
+	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
+	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" ue="" />'."\n".
+	'<input type="hidden" name="saveState"   value="" />'."\n".
+	'<input type="hidden" name="gradingMenu" value="1" />'."\n".
+	'<input type="hidden" name="showgrading" value="yes" />'."\n";
+
+    foreach my $menudata (@menu) {
+        if ($menudata->{'name'} ne &mt('Verify Receipt')) {
+            $Str .='    <h3><a '.
+                $menudata->{'jscript'}.
+                ' href="'.
+                $menudata->{'url'}.'" >'.
+                $menudata->{'name'}."</a></h3>\n";
+        } else {
+            $Str .='    <h3><input type="button" value="Verify Receipt" '.
+                $menudata->{'jscript'}.
+                ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
+                ' /></h3>';
+            $Str .= ('&nbsp;'x8).
+                    ' receipt: '.&Apache::lonnet::recprefix($env{'request.course.id'}).
+                    '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />';
+        }
+        $Str .= '    '.('&nbsp;'x8).$menudata->{'short_description'}.
+            "\n";
+    }
+    $Str .="</dl>\n";
+    $Str .="</form>\n";
+    $request->print(<<GRADINGMENUJS);
+<script type="text/javascript" language="javascript">
+    function checkChoice(formname,val,cmdx) {
+	if (val <= 2) {
+	    var cmd = radioSelection(formname.radioChoice);
+	    var cmdsave = cmd;
+	} else {
+	    cmd = cmdx;
+	    cmdsave = 'submission';
+	}
+	formname.command.value = cmd;
+	if (val < 5) formname.submit();
+	if (val == 5) {
+	    if (!checkReceiptNo(formname,'notOK')) { 
+	        return false;
+	    } else {
+	        formname.submit();
+	    }
+	}
+    }
+
+    function checkReceiptNo(formname,nospace) {
+	var receiptNo = formname.receipt.value;
+	var checkOpt = false;
+	if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
+	if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
+	if (checkOpt) {
+	    alert("Please enter a receipt number given by a student in the receipt box.");
+	    formname.receipt.value = "";
+	    formname.receipt.focus();
+	    return false;
+	}
+	return true;
+    }
+</script>
+GRADINGMENUJS
+    &commonJSfunctions($request);
+    return $Str;    
+}
+
+
+#--- Displays the submissions first page -------
+sub submit_options {
     my ($request) = @_;
     my ($symb)=&get_symb($request);
     if (!$symb) {return '';}
@@ -6014,7 +7372,7 @@ GRADINGMENUJS
     my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
 
     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
-	'<input type="hidden" name="symb"        value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
 	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
 	'<input type="hidden" name="command"     value="" />'."\n".
@@ -6022,14 +7380,20 @@ GRADINGMENUJS
 	'<input type="hidden" name="gradingMenu" value="1" />'."\n".
 	'<input type="hidden" name="showgrading" value="yes" />'."\n";
 
-    $result.='<table width="100%" border="0"><tr><td bgcolor=#777777>'."\n".
-	'<table width="100%" border="0"><tr bgcolor="#e6ffff"><td colspan="2">'."\n".
+    $result.='<table border="0"><tr><td bgcolor=#777777>'."\n".
+	'<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n".
 	'&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".
 	'<tr bgcolor="#ffffe6" valign="top"><td>'."\n";
 
     $result.='<table width="100%" border="0">';
+    $result.='<tr bgcolor="#ffffe6" valign="top">'."\n";
+    $result.='<td><b>'.&mt('Sections').'</b></td>';
+    $result.='<td><b>'.&mt('Groups').'</b></td>';
+    $result.='<td><b>'.&mt('Access Status').'</td>'."\n";
+    $result.='<td><b>'.&mt('Submission Status').'</td>'."\n";
+    $result.='</tr>';
     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".
-	'&nbsp;'.&mt('Select Section').': <select name="section">'."\n";
+	'&nbsp;<select name="section" multiple="multiple" size="3">'."\n";
     if (ref($sections)) {
 	foreach (sort (@$sections)) {
 	    $result.='<option value="'.$_.'" '.
@@ -6037,15 +7401,13 @@ GRADINGMENUJS
 	}
     }
     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
+    $result.= '</td><td>'."\n";
+    $result.= &Apache::lonstatistics::GroupSelect('group','multiple',3);
+    $result.='</td><td>'."\n";
+    $result.=&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,3,undef,'mult');
 
-    $result.=&mt('Student Status').':'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef);
-
-    $result.='</td></tr>';
-
-    $result.='<tr bgcolor="#ffffe6"valign="top"><td><label>'.
-	'<input type="radio" name="radioChoice" value="submission" '.
-	($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').
-	'</label> <select name="submitonly">'.
+    $result.='</td>';
+    $result.='<td><select name="submitonly" size="3">'.
 	'<option value="yes" '.
 	($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>'.
 	'<option value="queued" '.
@@ -6055,54 +7417,64 @@ GRADINGMENUJS
 	'<option value="incorrect" '.
 	($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>'.
 	'<option value="all" '.
-	($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>'."\n";
+	($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>';
 
-    $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
+    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3"><label>'.
+	'<input type="radio" name="radioChoice" value="submission" '.
+	($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').
+	'</label> </td></tr>'."\n";
+
+    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3">'.
 	'<label><input type="radio" name="radioChoice" value="viewgrades" '.
 	($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
 	'<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n";
 
-    $result.='<tr bgcolor="#ffffe6" valign="top"><td>'.
-	'<label><input type="radio" name="radioChoice" value="pickStudentPage" '.
+    $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'.
+	'<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.
+	'</td></tr>'."\n";
+
+
+    $result.='<tr bgcolor="#ffffe6" valign="top"><td colspan="3">'.
+	'<br /><label><input type="radio" name="radioChoice" value="pickStudentPage" '.
 	($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
-	'The <b>complete</b> set/page/sequence: For one student</label></td></tr>'."\n";
+	'The <b>complete</b> set/page/sequence/folder: For one student</label></td></tr>'."\n";
 
-    $result.='<tr bgcolor="#ffffe6"><td><br />'.
+    $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'.
 	'<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.
 	'</td></tr></table>'."\n";
 
-    $result.='</td><td valign="top">';
+    $result.='</td>'; #<td valign="top">';
 
-    $result.='<table width="100%" border="0">';
-    $result.='<tr bgcolor="#ffffe6"><td>'.
-	'<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.
-	' '.&mt('scores from file').' </td></tr>'."\n";
-
-    $result.='<tr bgcolor="#ffffe6"><td>'.
-        '<input type="button" onClick="javascript:checkChoice(this.form,\'6\',\'processclicker\');" value="'.&mt('Process').'" />'.
-        ' '.&mt('clicker file').' </td></tr>'."\n";
-
-    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
-	'<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'.
-	'" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n";
-
-    if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) {
-	$result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
-	    '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'.
-	    ' '.&mt('receipt').': '.
-	    &Apache::lonnet::recprefix($env{'request.course.id'}).
-	    '-<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.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
-	'<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'.
-	'" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n";
-
-    $result.='</table>'."\n".
-	'</td></tr></table>'."\n".
+#    $result.='<table width="100%" border="0">';
+#    $result.='<tr bgcolor="#ffffe6"><td>'.
+#	'<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.
+#	' '.&mt('scores from file').' </td></tr>'."\n";
+#
+#    $result.='<tr bgcolor="#ffffe6"><td>'.
+#        '<input type="button" onClick="javascript:checkChoice(this.form,\'6\',\'processclicker\');" value="'.&mt('Process').'" />'.
+#        ' '.&mt('clicker file').' </td></tr>'."\n";
+#
+#    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
+#	'<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'.
+#	'" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n";
+#
+#    if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) {
+#	$result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
+#	    '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'.
+#	    ' '.&mt('receipt').': '.
+#	    &Apache::lonnet::recprefix($env{'request.course.id'}).
+#	    '-<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.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
+#	'<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'.
+#	'" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n";
+#
+#    $result.='</table>'."\n".'</td>';
+    $result.= '</tr></table>'."\n".
 	'</td></tr></table></form>'."\n";
     return $result;
 }
@@ -6130,53 +7502,64 @@ sub init_perm {
 }
 
 sub gather_clicker_ids {
-    my %clickerids=();
+    my %clicker_ids;
 
     my $classlist = &Apache::loncoursedata::get_classlist();
 
     # Set up a couple variables.
-    my $usernameidx = &Apache::loncoursedata::CL_SNAME();
-    my $domainidx   = &Apache::loncoursedata::CL_SDOM();
+    my $username_idx = &Apache::loncoursedata::CL_SNAME();
+    my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
+    my $status_idx   = &Apache::loncoursedata::CL_STATUS();
 
-    foreach my $student (keys %$classlist) {
-
-        my $username = $classlist->{$student}->[$usernameidx];
-        my $domain   = $classlist->{$student}->[$domainidx];
+    foreach my $student (keys(%$classlist)) {
+        if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
+        my $username = $classlist->{$student}->[$username_idx];
+        my $domain   = $classlist->{$student}->[$domain_idx];
         my $clickers =
-	   (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
+	    (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
         foreach my $id (split(/\,/,$clickers)) {
-            $id=~s/^0+//;
-            if (exists($clickerids{$id})) {
-               $clickerids{$id}.=','.$username.':'.$domain;
+            $id=~s/^[\#0]+//;
+            $id=~s/[\-\:]//g;
+            if (exists($clicker_ids{$id})) {
+		$clicker_ids{$id}.=','.$username.':'.$domain;
             } else {
-               $clickerids{$id}=$username.':'.$domain;
+		$clicker_ids{$id}=$username.':'.$domain;
             }
         }
     }
-    return %clickerids;
+    return %clicker_ids;
 }
 
 sub gather_adv_clicker_ids {
-    my %clickerids=();
+    my %clicker_ids;
     my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
-    foreach my $element (sort keys %coursepersonnel) {
+    foreach my $element (sort(keys(%coursepersonnel))) {
         foreach my $person (split(/\,/,$coursepersonnel{$element})) {
             my ($puname,$pudom)=split(/\:/,$person);
             my $clickers =
-               (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
+		(&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
             foreach my $id (split(/\,/,$clickers)) {
-               $id=~s/^0+//;
-               if (exists($clickerids{$id})) {
-                  $clickerids{$id}.=','.$puname.':'.$pudom;
-               } else {
-                  $clickerids{$id}=$puname.':'.$pudom;
-               }
+		$id=~s/^[\#0]+//;
+                $id=~s/[\-\:]//g;
+		if (exists($clicker_ids{$id})) {
+		    $clicker_ids{$id}.=','.$puname.':'.$pudom;
+		} else {
+		    $clicker_ids{$id}=$puname.':'.$pudom;
+		}
             }
         }
     }
-    return %clickerids;
+    return %clicker_ids;
+}
+
+sub clicker_grading_parameters {
+    return ('gradingmechanism' => 'scalar',
+            'upfiletype' => 'scalar',
+            'specificid' => 'scalar',
+            'pcorrect' => 'scalar',
+            'pincorrect' => 'scalar');
 }
 
 sub process_clicker {
@@ -6192,16 +7575,33 @@ sub process_clicker {
     $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource').
         '.</b></td></tr>'."\n";
     $result.='<tr bgcolor=#ffffe6><td>'."\n";
+# Attempt to restore parameters from last session, set defaults if not present
+    my %Saveable_Parameters=&clicker_grading_parameters();
+    &Apache::loncommon::restore_course_settings('grades_clicker',
+                                                 \%Saveable_Parameters);
+    if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
+    if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
+    if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
+    if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
+
+    my %checked;
+    foreach my $gradingmechanism ('attendance','personnel','specific') {
+       if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
+          $checked{$gradingmechanism}="checked='checked'";
+       }
+    }
+
     my $upload=&mt("Upload File");
     my $type=&mt("Type");
     my $attendance=&mt("Award points just for participation");
     my $personnel=&mt("Correctness determined from response by course personnel");
-    my $specific=&mt("Correctness determined from response with clicker ID"); 
+    my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
     my $pcorrect=&mt("Percentage points for correct solution");
     my $pincorrect=&mt("Percentage points for incorrect solution");
-    my $selectform=&Apache::loncommon::select_form('iclicker','upfiletype',
-                                  ('iclicker' => 'i>clicker'));
-
+    my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
+						   ('iclicker' => 'i>clicker',
+                                                    'interwrite' => 'interwrite PRS'));
+    $symb = &Apache::lonenc::check_encrypt($symb);
     $result.=<<ENDUPFORM;
 <script type="text/javascript">
 function sanitycheck() {
@@ -6249,13 +7649,13 @@ function sanitycheck() {
 <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 <input type="file" name="upfile" size="50" />
 <br /><label>$type: $selectform</label>
-<br /><label>$attendance: <input type="radio" name="gradingmechanism" value="attendance" checked="checked" onClick="sanitycheck()" /></label>
-<br /><label>$personnel: <input type="radio" name="gradingmechanism" value="personnel" onClick="sanitycheck()" /></label>
-<br /><label>$specific: <input type="radio" name="gradingmechanism" value="specific" onClick="sanitycheck()" /></label>
-<input type="text" name="specificid" size="15" />
-<input type="hidden" name="waschecked" value="attendance" />
-<br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="100" onChange="sanitycheck()" /></label>
-<br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="100" onChange="sanitycheck()" /></label>
+<br /><label><input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" />$attendance </label>
+<br /><label><input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" />$personnel</label>
+<br /><label><input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" />$specific </label>
+<input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
+<input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
+<br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>
+<br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>
 <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
 </form>
 ENDUPFORM
@@ -6269,84 +7669,324 @@ sub process_clicker_file {
     my ($r)=@_;
     my ($symb)=&get_symb($r);
     if (!$symb) {return '';}
+
+    my %Saveable_Parameters=&clicker_grading_parameters();
+    &Apache::loncommon::store_course_settings('grades_clicker',
+                                              \%Saveable_Parameters);
+
     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
     if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
-       $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
-       return $result.&show_grading_menu_form($symb);
+	$result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
+	return $result.&show_grading_menu_form($symb);
     }
-    my %clickerids=&gather_clicker_ids();
-    my %correctids=();
+    my %clicker_ids=&gather_clicker_ids();
+    my %correct_ids;
     if ($env{'form.gradingmechanism'} eq 'personnel') {
-       %correctids=&gather_adv_clicker_ids();
+	%correct_ids=&gather_adv_clicker_ids();
     }
     if ($env{'form.gradingmechanism'} eq 'specific') {
-       my $correctid=$env{'form.specificid'};
-       $correctid=~tr/a-z/A-Z/;
-       $correctid=~s/\s//gs;
-       $correctid=~s/^0+//;
-       $correctids{$correctid}='specified';
+	foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
+	   $correct_id=~tr/a-z/A-Z/;
+	   $correct_id=~s/\s//gs;
+	   $correct_id=~s/^[\#0]+//;
+           $correct_id=~s/[\-\:]//g;
+           if ($correct_id) {
+	      $correct_ids{$correct_id}='specified';
+           }
+        }
     }
     if ($env{'form.gradingmechanism'} eq 'attendance') {
-       $result.=&mt('Score based on attendance only');
+	$result.=&mt('Score based on attendance only');
     } else {
-       my $number=0;
-       $result.='<h3>'.&mt('Correctness determined by the following IDs').'</h3>';
-       foreach my $id (sort(keys(%correctids))) {
-          $result.='<tt>'.$id.'</tt> - ';
-          if ($correctids{$id} eq 'specified') {
-             $result.=&mt('specified');
-          } else {
-             my ($uname,$udom)=split(/\:/,$correctids{$id});
-             $result.=&Apache::loncommon::plainname($uname,$udom);
-          }
-          $result.='<br />';
-          $number++;
-       }
-       if ($number==0) {
-          $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
-          return $result.&show_grading_menu_form($symb);
-       }
+	my $number=0;
+	$result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
+	foreach my $id (sort(keys(%correct_ids))) {
+	    $result.='<br /><tt>'.$id.'</tt> - ';
+	    if ($correct_ids{$id} eq 'specified') {
+		$result.=&mt('specified');
+	    } else {
+		my ($uname,$udom)=split(/\:/,$correct_ids{$id});
+		$result.=&Apache::loncommon::plainname($uname,$udom);
+	    }
+	    $number++;
+	}
+        $result.="</p>\n";
+	if ($number==0) {
+	    $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
+	    return $result.&show_grading_menu_form($symb);
+	}
     }
     if (length($env{'form.upfile'}) < 2) {
-        $result.="<span class=\"LC_error\">Error:</span> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.";
+        $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.',
+		     '<span class="LC_error">',
+		     '</span>',
+		     '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');
         return $result.&show_grading_menu_form($symb);
     }
-    my %contents=();
+
+# Were able to get all the info needed, now analyze the file
+
+    $result.=&Apache::loncommon::studentbrowser_javascript();
+    $symb = &Apache::lonenc::check_encrypt($symb);
+    my $heading=&mt('Scanning clicker file');
+    $result.=(<<ENDHEADER);
+<br /><table width="100%" border="0"><tr><td bgcolor="#777777">
+<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
+<b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
+<form method="post" action="/adm/grades" name="clickeranalysis">
+<input type="hidden" name="symb" value="$symb" />
+<input type="hidden" name="command" value="assignclickergrades" />
+<input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
+<input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
+<input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
+<input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
+<input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
+ENDHEADER
+    my %responses;
+    my @questiontitles;
     my $errormsg='';
     my $number=0;
     if ($env{'form.upfiletype'} eq 'iclicker') {
-       ($errormsg,$number,%contents)=&iclicker_eval();
-       $result.=$errormsg;
+	($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
+    }
+    if ($env{'form.upfiletype'} eq 'interwrite') {
+        ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
     }
+    $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
+             '<input type="hidden" name="number" value="'.$number.'" />'.
+             &mt('Awarding [_1] percent for corrion(s)',$number).'<br />'.
+             '<input type="hidden" name="number" value="'.$number.'" />'.
+             &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
+                 $env{'form.pcorrect'},$env{'form.pincorrect'}).
+             '<br />';
+# Remember Question Titles
+# FIXME: Possibly need delimiter other than ":"
+    for (my $i=0;$i<$number;$i++) {
+        $result.='<input type="hidden" name="question:'.$i.'" value="'.
+                 &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
+    }
+    my $correct_count=0;
+    my $student_count=0;
+    my $unknown_count=0;
+# Match answers with usernames
+# FIXME: Possibly need delimiter other than ":"
+    foreach my $id (keys(%responses)) {
+       if ($correct_ids{$id}) {
+          $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
+          $correct_count++;
+       } elsif ($clicker_ids{$id}) {
+          if ($clicker_ids{$id}=~/\,/) {
+# More than one user with the same clicker!
+             $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
+             $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
+                           "<select name='multi".$id."'>";
+             foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
+                 $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
+             }
+             $result.='</select>';
+             $unknown_count++;
+          } else {
+# Good: found one and only one user with the right clicker
+             $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
+             $student_count++;
+          }
+       } else {
+          $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
+          $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
+                   "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
+                   "\n".&mt("Domain").": ".
+                   &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
+                   &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);
+          $unknown_count++;
+       }
+    }
+    $result.='<hr />'.
+             &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
+    if ($env{'form.gradingmechanism'} ne 'attendance') {
+       if ($correct_count==0) {
+          $errormsg.="Found no correct answers answers for grading!";
+       } elsif ($correct_count>1) {
+          $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
+       }
+    }
+    if ($number<1) {
+       $errormsg.="Found no questions.";
+    }
+    if ($errormsg) {
+       $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
+    } else {
+       $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
+    }
+    $result.='</form></td></tr></table>'."\n".
+             '</td></tr></table><br /><br />'."\n";
     return $result.&show_grading_menu_form($symb);
 }
 
 sub iclicker_eval {
+    my ($questiontitles,$responses)=@_;
     my $number=0;
     my $errormsg='';
     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
-       chomp($line);
-       foreach my $quoted ($line=~/\,\s*\"([^\"]*)\"\s*\,/g) {
-          my $replace=$quoted;
-          $replace=~s/\,//g;
-          &Apache::lonnet::logthis($quoted.' - '.$replace.'<br />');
-          $line=~s/\,\s*\"\Q$quoted\E\"\s*\,/,$replace,/gs;
+        my %components=&Apache::loncommon::record_sep($line);
+        my @entries=map {$components{$_}} (sort(keys(%components)));
+	if ($entries[0] eq 'Question') {
+	    for (my $i=3;$i<$#entries;$i+=6) {
+		$$questiontitles[$number]=$entries[$i];
+		$number++;
+	    }
+	}
+	if ($entries[0]=~/^\#/) {
+	    my $id=$entries[0];
+	    my @idresponses;
+	    $id=~s/^[\#0]+//;
+	    for (my $i=0;$i<$number;$i++) {
+		my $idx=3+$i*6;
+		push(@idresponses,$entries[$idx]);
+	    }
+	    $$responses{$id}=join(',',@idresponses);
+	}
+    }
+    return ($errormsg,$number);
+}
+
+sub interwrite_eval {
+    my ($questiontitles,$responses)=@_;
+    my $number=0;
+    my $errormsg='';
+    my $skipline=1;
+    my $questionnumber=0;
+    my %idresponses=();
+    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[1] eq 'Time') { $skipline=0; next; }
+        if ($entries[1] eq 'Response') { $skipline=1; }
+        next if $skipline;
+        if ($entries[0]!=$questionnumber) {
+           $questionnumber=$entries[0];
+           $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
+           $number++;
+        }
+        my $id=$entries[4];
+        $id=~s/^[\#0]+//;
+        $id=~s/^v\d*\://i;
+        $id=~s/[\-\:]//g;
+        $idresponses{$id}[$number]=$entries[6];
+    }
+    foreach my $id (keys %idresponses) {
+       $$responses{$id}=join(',',@{$idresponses{$id}});
+       $$responses{$id}=~s/^\s*\,//;
+    }
+    return ($errormsg,$number);
+}
+
+sub assign_clicker_grades {
+    my ($r)=@_;
+    my ($symb)=&get_symb($r);
+    if (!$symb) {return '';}
+# See which part we are saving to
+    my ($partlist,$handgrade,$responseType) = &response_type($symb);
+# FIXME: This should probably look for the first handgradeable part
+    my $part=$$partlist[0];
+# Start screen output
+    my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
+
+    my $heading=&mt('Assigning grades based on clicker file');
+    $result.=(<<ENDHEADER);
+<br /><table width="100%" border="0"><tr><td bgcolor="#777777">
+<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
+<b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
+ENDHEADER
+# Get correct result
+# FIXME: Possibly need delimiter other than ":"
+    my @correct=();
+    my $gradingmechanism=$env{'form.gradingmechanism'};
+    my $number=$env{'form.number'};
+    if ($gradingmechanism ne 'attendance') {
+       foreach my $key (keys(%env)) {
+          if ($key=~/^form\.correct\:/) {
+             my @input=split(/\,/,$env{$key});
+             for (my $i=0;$i<=$#input;$i++) {
+                 if (($correct[$i]) && ($input[$i]) &&
+                     ($correct[$i] ne $input[$i])) {
+                    $result.='<br /><span class="LC_warning">'.
+                             &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
+                                 $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
+                 } elsif ($input[$i]) {
+                    $correct[$i]=$input[$i];
+                 }
+             }
+          }
        }
-       my @entries=split(/\,/,$line);
-       if ($entries[0] eq 'Question') {
-          for (my $i=3;$i<$#entries;$i+=6) {
-              $number++;
-              $errormsg.='<br />'.$entries[$i];
+       for (my $i=0;$i<$number;$i++) {
+          if (!$correct[$i]) {
+             $result.='<br /><span class="LC_error">'.
+                      &mt('No correct result given for question "[_1]"!',
+                          $env{'form.question:'.$i}).'</span>';
           }
        }
+       $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));
     }
-    return ($errormsg,$number);
+# Start grading
+    my $pcorrect=$env{'form.pcorrect'};
+    my $pincorrect=$env{'form.pincorrect'};
+    my $storecount=0;
+    foreach my $key (keys(%env)) {
+       my $user='';
+       if ($key=~/^form\.student\:(.*)$/) {
+          $user=$1;
+       }
+       if ($key=~/^form\.unknown\:(.*)$/) {
+          my $id=$1;
+          if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
+             $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
+          } elsif ($env{'form.multi'.$id}) {
+             $user=$env{'form.multi'.$id};
+          }
+       }
+       if ($user) { 
+          my @answer=split(/\,/,$env{$key});
+          my $sum=0;
+          for (my $i=0;$i<$number;$i++) {
+             if ($answer[$i]) {
+                if ($gradingmechanism eq 'attendance') {
+                   $sum+=$pcorrect;
+                } else {
+                   if ($answer[$i] eq $correct[$i]) {
+                      $sum+=$pcorrect;
+                   } else {
+                      $sum+=$pincorrect;
+                   }
+                }
+             }
+          }
+          my $ave=$sum/(100*$number);
+# Store
+          my ($username,$domain)=split(/\:/,$user);
+          my %grades=();
+          $grades{"resource.$part.solved"}='correct_by_override';
+          $grades{"resource.$part.awarded"}=$ave;
+          $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
+          my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
+                                                 $env{'request.course.id'},
+                                                 $domain,$username);
+          if ($returncode ne 'ok') {
+             $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
+          } else {
+             $storecount++;
+          }
+       }
+    }
+# We are done
+    $result.='<br />'.&mt('Successfully stored grades for [_1] student(s).',$storecount).
+             '</td></tr></table>'."\n".
+             '</td></tr></table><br /><br />'."\n";
+    return $result.&show_grading_menu_form($symb);
 }
 
 sub handler {
     my $request=$_[0];
-
-    &reset_perm();
+    &reset_caches();
     if ($env{'browser.mathml'}) {
 	&Apache::loncommon::content_type($request,'text/xml');
     } else {
@@ -6358,9 +7998,12 @@ sub handler {
     my $symb=&get_symb($request,1);
     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));
     }
+
+
     $request->print(&Apache::loncommon::start_page('Grading'));
     if ($symb eq '' && $command eq '') {
 	if ($env{'user.adv'}) {
@@ -6401,7 +8044,9 @@ sub handler {
 	} elsif ($command eq 'processGroup' && $perm{'vgr'}) {
 	    &processGroup($request);
 	} elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
-	    $request->print(&gradingmenu($request));
+	    $request->print(&grading_menu($request));
+	} elsif ($command eq 'submit_options' && $perm{'vgr'}) {
+	    $request->print(&submit_options($request));
 	} elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
 	    $request->print(&viewgrades($request));
 	} elsif ($command eq 'handgrade' && $perm{'mgr'}) {
@@ -6414,6 +8059,8 @@ sub handler {
             $request->print(&process_clicker($request));
         } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
             $request->print(&process_clicker_file($request));
+        } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
+            $request->print(&assign_clicker_grades($request));
 	} elsif ($command eq 'csvform' && $perm{'mgr'}) {
 	    $request->print(&upcsvScores_form($request));
 	} elsif ($command eq 'csvupload' && $perm{'mgr'}) {
@@ -6457,6 +8104,7 @@ sub handler {
 	}
     }
     $request->print(&Apache::loncommon::end_page());
+    &reset_caches();
     return '';
 }