--- loncom/homework/grades.pm	2007/07/24 21:21:31	1.423
+++ loncom/homework/grades.pm	2007/10/01 19:41:51	1.442
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.423 2007/07/24 21:21:31 albertel Exp $
+# $Id: grades.pm,v 1.442 2007/10/01 19:41:51 banghart Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -45,36 +45,26 @@ 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"
+
 
 # ----- These first few routines are general use routines.----
 #
 # --- 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); }
 	}
@@ -195,22 +185,54 @@ sub showResourceInfo {
     return $result,$responseType,$hdgrade,$partlist,$handgrade;
 }
 
+sub reset_caches {
+    &reset_analyze_cache();
+    &reset_perm();
+}
 
-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"});
+{
+    my %analyze_cache;
+
+    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 +281,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 +349,10 @@ sub cleanRecord {
 	    $result.='</ul>';
 	    return $result;
 	}
-       
+    } elsif ( $response =~ m/(?:numerical|formula)/) {
+	$answer = 
+	    &Apache::loncommon::format_previous_attempt_value('submission',
+							      $answer);
     }
     return $answer;
 }
@@ -373,6 +398,7 @@ COMMONJSFUNCTIONS
 sub getclasslist {
     my ($getsec,$filterlist) = @_;
     my @getsec;
+    my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     if (!ref($getsec)) {
 	if ($getsec ne '' && $getsec ne 'all') {
 	    @getsec=($getsec);
@@ -402,8 +428,8 @@ sub getclasslist {
         my $status   = 
             $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
 	# filter students according to status selected
-	if ($filterlist && $env{'form.Status'} ne 'Any') {
-	    if ($env{'form.Status'} ne $status) {
+	if ($filterlist && (!($stu_status =~ /Any/))) {
+	    if (!($stu_status =~ $status)) {
 		delete ($classlist->{$student});
 		next;
 	    }
@@ -485,6 +511,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".
@@ -496,7 +523,7 @@ sub jscriptNform {
 	'<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".
@@ -538,7 +565,7 @@ sub compute_points {
 #
 
 sub most_similar {
-    my ($uname,$udom,$uessay)=@_;
+    my ($uname,$udom,$uessay,$old_essays)=@_;
 
 # ignore spaces and punctuation
 
@@ -555,23 +582,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);
@@ -722,10 +748,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,8 +761,7 @@ 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".
@@ -747,7 +771,7 @@ LISTJAVASCRIPT
 	'<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 />';
@@ -1663,6 +1687,19 @@ 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) = @_;
@@ -1671,7 +1708,6 @@ sub submission {
     $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 +1727,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 +1760,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,11 +1779,11 @@ 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".
@@ -1757,7 +1794,7 @@ sub submission {
 			'<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"'.
@@ -1805,12 +1842,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 (int($counter/2) eq $counter) { $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';
@@ -1943,12 +1985,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 />';
@@ -2071,6 +2122,11 @@ 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";
@@ -2954,23 +3010,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="'.&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.
@@ -3076,8 +3134,12 @@ sub viewgrades {
 	'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;
@@ -3154,9 +3216,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">'.
@@ -3354,7 +3417,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);
 }
 
@@ -3857,9 +3920,10 @@ 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="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
@@ -4357,9 +4421,47 @@ sub updateGradeByPage {
 
 =head1 Bubble sheet grading routines
 
-  (For this documentation 'scanline' refers to the full line of characters
-   from the file that we are parsing 'bubble line' refers to the data
-   representing the line of bubbles that are on the physical bubble sheet)
+  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
 
@@ -4531,7 +4633,7 @@ sub scantron_CODEunique {
 
   Generates the initial screen to start the bubble sheet process.
   Allows for - starting a grading run.
-             - downloading exisiting scan data (original, corrected
+             - downloading existing scan data (original, corrected
                                                 or skipped info)
 
              - uploading new scan data
@@ -4589,7 +4691,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>
@@ -4732,17 +4834,18 @@ SCANTRONFORM
                     'questions' start
       Qlength     - number of columns comprising a single bubble line from
                     the sheet. (usually either 1 or 10)
-      Qon         - either a single charater representing the character used
+      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 buble was left blank
+      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 firs tname starts in
+      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
@@ -4793,7 +4896,7 @@ sub get_scantron_config {
 
     $classlist - reference to the class list hash. This is a hash
                  keyed by student name:domain  whose elements are references
-                 to arrays containng various chunks of information
+                 to arrays containing various chunks of information
                  about the student. (See loncoursedata for more info).
 
   Returns
@@ -4813,7 +4916,7 @@ sub username_to_idmap {
 
 =pod
 
-=item scatron_fixup_scanline
+=item scantron_fixup_scanline
 
    Process a requested correction to a scanline.
 
@@ -4832,12 +4935,12 @@ sub username_to_idmap {
    $args               - hash of additional info,
                           - 'ID' 
                                'newid' -> studentID to use in replacement
-                                          of exisiting one
+                                          of existing one
                           - 'CODE' 
                                'CODE_ignore_dup' - set to true if duplicates
                                                    should be ignored.
 	                       'CODE' - is new code or 'use_unfound'
-                                        if the exisitng unfound code should
+                                        if the existing unfound code should
                                         be used as is
                           - 'answer'
                                'response' - new answer or 'none' if blank
@@ -4920,7 +5023,7 @@ sub scantron_fixup_scanline {
   Arguments:
     $scan_data  - The hash (see scantron_getfile)
     $key        - shorthand of the key to edit (actual key is
-                  scatronfilename_key).
+                  scantronfilename_key).
     $data        - New value of the hash entry.
     $delete      - If true, the entry is removed from the hash.
 
@@ -5107,13 +5210,13 @@ sub scantron_parse_scanline {
    queue of messages to be shown after grading pass is complete
 
  Arguments:
-   $delayqueue  - arrary ref of hash ref of erro messages
+   $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 $dealyqueue to have a new hash ref of the error
+   updates the $delayqueue to have a new hash ref of the error
 
 =cut
 
@@ -5129,6 +5232,18 @@ sub scantron_add_delay {
 
 =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 {
@@ -5149,6 +5264,9 @@ sub scantron_find_student {
 
 =item scantron_filter
 
+   Filter sub for lonnavmaps, filters out hidden resources if ignore
+   hidden resources was selected
+
 =cut
 
 sub scantron_filter {
@@ -5171,6 +5289,9 @@ sub scantron_filter {
 
 =item scantron_process_corrections
 
+   Gets correction information out of submitted form data and corrects
+   the scanline
+
 =cut
 
 sub scantron_process_corrections {
@@ -5234,6 +5355,10 @@ sub scantron_process_corrections {
 
 =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 {
@@ -5246,6 +5371,8 @@ sub reset_skipping_status {
 
 =item start_skipping
 
+   Marks a scanline to be skipped. 
+
 =cut
 
 sub start_skipping {
@@ -5263,6 +5390,8 @@ sub start_skipping {
 
 =item should_be_skipped
 
+   Checks whether a scanline should be skipped.
+
 =cut
 
 sub should_be_skipped {
@@ -5284,6 +5413,9 @@ sub should_be_skipped {
 
 =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 {
@@ -5303,6 +5435,10 @@ sub remember_current_skipped {
 
 =item check_for_error
 
+    Checks if there was an error when attempting to remove a specific
+    scantron_.. bubble sheet data file. Prints out an error if
+    something went wrong.
+
 =cut
 
 sub check_for_error {
@@ -5316,6 +5452,9 @@ sub check_for_error {
 
 =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 {
@@ -5354,6 +5493,9 @@ STUFF
 
 =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 {
@@ -5391,6 +5533,8 @@ STUFF
 
 =item scantron_form_start
 
+    html hidden input for remembering all selected grading options
+
 =cut
 
 sub scantron_form_start {
@@ -5414,6 +5558,12 @@ SCANTRONFORM
 
 =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 {
@@ -5423,7 +5573,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();
     }
@@ -5442,7 +5592,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();
@@ -5504,6 +5654,10 @@ STUFF
 
 =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 {
@@ -5525,6 +5679,11 @@ sub scantron_remove_file {
 
 =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 {
@@ -5554,6 +5713,44 @@ sub scantron_remove_scan_data {
 
 =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 {
@@ -5592,6 +5789,15 @@ sub scantron_getfile {
 
 =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 {
@@ -5607,6 +5813,16 @@ sub lonnet_putfile {
 
 =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 {
@@ -5633,6 +5849,22 @@ sub scantron_putfile {
 
 =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 {
@@ -5647,6 +5879,17 @@ sub scantron_get_line {
 
 =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 {
@@ -5664,6 +5907,19 @@ sub get_todo_count {
 
 =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 {
@@ -5680,6 +5936,15 @@ sub scantron_put_line {
 
 =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 {
@@ -5695,6 +5960,9 @@ sub scantron_clear_skip {
 
 =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 {
@@ -5717,6 +5985,9 @@ sub scantron_filter_not_exam {
 
 =item scantron_validate_sequence
 
+    Validates the selected sequence, checking for resource that are
+    not set to exam mode.
+
 =cut
 
 sub scantron_validate_sequence {
@@ -5746,6 +6017,9 @@ sub scantron_validate_sequence {
 
 =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 {
@@ -5814,6 +6088,30 @@ sub scantron_validate_ID {
 
 =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 {
@@ -5945,7 +6243,7 @@ ENDSCRIPT
 =item scantron_bubble_selector
   
    Generates the html radiobuttons to correct a single bubble line
-   possibly showing the exisiting the selected bubbles if known
+   possibly showing the existing the selected bubbles if known
 
  Arguments:
     $r           - Apache request object
@@ -6021,6 +6319,16 @@ sub scantron_bubble_selector {
 
 =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 {
@@ -6038,6 +6346,20 @@ sub num_matches {
 
 =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 {
@@ -6054,6 +6376,17 @@ sub scantron_get_closely_matching_CODEs
 
 =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 {
@@ -6082,6 +6415,10 @@ sub get_codes {
 
 =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 {
@@ -6139,6 +6476,9 @@ sub scantron_validate_CODE {
 
 =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 {
@@ -6168,9 +6508,17 @@ sub scantron_validate_doublebubble {
 
 =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 result to $env{'form.scantron_maxbubble'}
+
 =cut
 
 sub scantron_get_maxbubble {    
+
     if (defined($env{'form.scantron_maxbubble'}) &&
 	$env{'form.scantron_maxbubble'}) {
 	return $env{'form.scantron_maxbubble'};
@@ -6185,14 +6533,40 @@ sub scantron_get_maxbubble {
 
     &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 = ();
+
     foreach my $resource (@resources) {
+	my $symb = $resource->symb();
 	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 $bubble_lines = $analysis{"$part_id.bubble_lines"}[0];
+	    if (!$bubble_lines) {
+		$bubble_lines = 1;
+	    }
+	    $bubble_lines_per_response{"$symb.$part_id"} = $bubble_lines;
+	    $total_lines = $total_lines + $bubble_lines;
+	}
+
     }
     &Apache::lonnet::delenv('scantron\.');
     $env{'form.scantron_maxbubble'} =
-	&Apache::lonxml::get_problem_counter()-1;
-
+	$total_lines;
     return $env{'form.scantron_maxbubble'};
 }
 
@@ -6200,6 +6574,9 @@ sub scantron_get_maxbubble {
 
 =item scantron_validate_missingbubbles
 
+   Validates all scanlines in the selected file to not have any
+   bubble lines with missing bubbles that haven't been verified as missing.
+
 =cut
 
 sub scantron_validate_missingbubbles {
@@ -6474,7 +6851,7 @@ sub scantron_upload_scantron_data_save {
 
 =item valid_file
 
-   Vaildates that the requested bubble data file has exists in the course.
+   Validates that the requested bubble data file exists in the course.
 
 =cut
 
@@ -6637,8 +7014,13 @@ GRADINGMENUJS
 	'<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>Groups</td>';
+    $result.='<td><b>'.&mt('Access 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="'.$_.'" '.
@@ -6646,12 +7028,14 @@ GRADINGMENUJS
 	}
     }
     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
-
-    $result.=&mt('Student Status').':'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef);
+#    $result.= '</td><td>'."\n";
+#    $result.='Put group select here'."\n";
+    $result.='</td><td>'."\n";
+    $result.=&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,3,undef,'mult');
 
     $result.='</td></tr>';
 
-    $result.='<tr bgcolor="#ffffe6"valign="top"><td><label>'.
+    $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> <select name="submitonly">'.
@@ -6666,17 +7050,17 @@ GRADINGMENUJS
 	'<option value="all" '.
 	($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>'."\n";
 
-    $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
+    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
 	'<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>'.
+    $result.='<tr bgcolor="#ffffe6" valign="top"><td colspan="2">'.
 	'<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";
 
-    $result.='<tr bgcolor="#ffffe6"><td><br />'.
+    $result.='<tr bgcolor="#ffffe6"><td colspan="2"><br />'.
 	'<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.
 	'</td></tr></table>'."\n";
 
@@ -6746,9 +7130,10 @@ sub gather_clicker_ids {
     # Set up a couple variables.
     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)) {
-
+        if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
         my $username = $classlist->{$student}->[$username_idx];
         my $domain   = $classlist->{$student}->[$domain_idx];
         my $clickers =
@@ -7009,8 +7394,21 @@ ENDHEADER
           $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
           $correct_count++;
        } elsif ($clicker_ids{$id}) {
-          $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
-          $student_count++;
+          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}.'" />'.
@@ -7030,6 +7428,9 @@ ENDHEADER
           $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 {
@@ -7158,6 +7559,8 @@ ENDHEADER
           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) { 
@@ -7203,7 +7606,7 @@ ENDHEADER
 sub handler {
     my $request=$_[0];
 
-    &reset_perm();
+    &reset_caches();
     if ($env{'browser.mathml'}) {
 	&Apache::loncommon::content_type($request,'text/xml');
     } else {
@@ -7316,6 +7719,7 @@ sub handler {
 	}
     }
     $request->print(&Apache::loncommon::end_page());
+    &reset_caches();
     return '';
 }