--- loncom/homework/grades.pm	2009/03/06 16:13:29	1.554
+++ loncom/homework/grades.pm	2009/05/07 15:47:06	1.570
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.554 2009/03/06 16:13:29 raeburn Exp $
+# $Id: grades.pm,v 1.570 2009/05/07 15:47:06 www Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -234,28 +234,54 @@ sub reset_caches {
 
 {
     my %analyze_cache;
+    my %analyze_cache_formkeys;
 
     sub reset_analyze_cache {
 	undef(%analyze_cache);
+        undef(%analyze_cache_formkeys);
     }
 
     sub get_analyze {
-	my ($symb,$uname,$udom,$no_increment)=@_;
+	my ($symb,$uname,$udom,$no_increment,$add_to_hash)=@_;
 	my $key = "$symb\0$uname\0$udom";
-	return $analyze_cache{$key} if (exists($analyze_cache{$key}));
+	if (exists($analyze_cache{$key})) {
+            my $getupdate = 0;
+            if (ref($add_to_hash) eq 'HASH') {
+                foreach my $item (keys(%{$add_to_hash})) {
+                    if (ref($analyze_cache_formkeys{$key}) eq 'HASH') {
+                        if (!exists($analyze_cache_formkeys{$key}{$item})) {
+                            $getupdate = 1;
+                            last;
+                        }
+                    } else {
+                        $getupdate = 1;
+                    }
+                }
+            }
+            if (!$getupdate) {
+                return $analyze_cache{$key};
+            }
+        }
 
 	my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
 	$url=&Apache::lonnet::clutter($url);
-	my $subresult=&ssi_with_retries($url, $ssi_retries,
-					   ('grade_target' => 'analyze',
-					    'grade_domain' => $udom,
-					    'grade_symb' => $symb,
-					    'grade_courseid' => 
-					    $env{'request.course.id'},
-					    'grade_username' => $uname,
-                                            'grade_noincrement' => $no_increment));
+        my %form = ('grade_target'      => 'analyze',
+                    'grade_domain'      => $udom,
+                    'grade_symb'        => $symb,
+                    'grade_courseid'    =>  $env{'request.course.id'},
+                    'grade_username'    => $uname,
+                    'grade_noincrement' => $no_increment);
+        if (ref($add_to_hash)) {
+            %form = (%form,%{$add_to_hash});
+        } 
+	my $subresult=&ssi_with_retries($url, $ssi_retries,%form);
 	(undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
 	my %analyze=&Apache::lonnet::str2hash($subresult);
+        if (ref($add_to_hash) eq 'HASH') {
+            $analyze_cache_formkeys{$key} = $add_to_hash;
+        } else {
+            $analyze_cache_formkeys{$key} = {};
+        }
 	return $analyze_cache{$key} = \%analyze;
     }
 
@@ -268,19 +294,26 @@ sub reset_caches {
     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;
+        my $foils = &get_order($partid,$respid,$symb,$uname,$udom);
+        if (ref($foils) eq 'ARRAY') {
+	    foreach my $foil (@{$foils}) {
+	        if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
+		    return $foil;
+	        }
 	    }
 	}
     }
 
     sub scantron_partids_tograde {
-        my ($resource,$cid,$uname,$udom) = @_;
+        my ($resource,$cid,$uname,$udom,$check_for_randomlist) = @_;
         my (%analysis,@parts);
         if (ref($resource)) {
             my $symb = $resource->symb();
-            my $analyze = &get_analyze($symb,$uname,$udom);
+            my $add_to_form;
+            if ($check_for_randomlist) {
+                $add_to_form = { 'check_parts_withrandomlist' => 1,};
+            }
+            my $analyze = &get_analyze($symb,$uname,$udom,undef,$add_to_form);
             if (ref($analyze) eq 'HASH') {
                 %analysis = %{$analyze};
             }
@@ -811,12 +844,10 @@ sub listStudents {
 
     my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
 
-    my %lt = ( 'multiple' =>
-	       &mt("Please select a student or group of students before clicking on the Next button."),
-	       'single'   =>
-	       &mt("Please select the student before clicking on the Next button."),
-	       );
-    %lt = &Apache::lonlocal::texthash(%lt);
+    my %lt = &Apache::lonlocal::texthash (
+		'multiple' => 'Please select a student or group of students before clicking on the Next button.',
+		'single'   => 'Please select the student before clicking on the Next button.',
+	     );
     $request->print(<<LISTJAVASCRIPT);
 <script type="text/javascript" language="javascript">
     function checkSelect(checkBox) {
@@ -858,16 +889,17 @@ LISTJAVASCRIPT
     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
 	"\n".$table;
 	
-    $gradeTable .= 
-	'&nbsp;<b>'.&mt('View Problem Text').': </b>'.
-	    '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
-	    '<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n".
-	    '<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n";
-    $gradeTable .= 
-	'&nbsp;<b>'.&mt('View Answer').': </b>'.
-	    '<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n".
-	    '<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n".
-	    '<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n";
+    $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
+    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
+                  .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
+                  .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
+                  .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
+                  .&Apache::lonhtmlcommon::row_closure();
+    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
+                  .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
+                  .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
+                  .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
+                  .&Apache::lonhtmlcommon::row_closure();
 
     my $submission_options;
     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
@@ -882,19 +914,20 @@ LISTJAVASCRIPT
 	'<label><input type="radio" name="lastSub" value="last" /> '.&mt('last submission &amp; parts info').' </label>'."\n".
 	'<label><input type="radio" name="lastSub" value="datesub" /> '.&mt('by dates and submissions').' </label>'."\n".
 	'<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').'</label>';
-    $gradeTable .= 
-	'&nbsp;<b>'.&mt('Submissions').': </b>'.$submission_options.'<br />'."\n";
+    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions'))
+                  .$submission_options
+                  .&Apache::lonhtmlcommon::row_closure();
+
+    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments'))
+                  .'<select name="increment">'
+                  .'<option value="1">'.&mt('Whole Points').'</option>'
+                  .'<option value=".5">'.&mt('Half Points').'</option>'
+                  .'<option value=".25">'.&mt('Quarter Points').'</option>'
+                  .'<option value=".1">'.&mt('Tenths of a Point').'</option>'
+                  .'</select>'
+                  .&Apache::lonhtmlcommon::row_closure();
 
     $gradeTable .= 
-        '&nbsp;<b>'.&mt('Grading Increments').': </b>'.
-	    '<select name="increment">'.
-	    '<option value="1">'.&mt('Whole Points').'</option>'.
-	    '<option value=".5">'.&mt('Half Points').'</option>'.
-	    '<option value=".25">'.&mt('Quarter Points').'</option>'.
-	    '<option value=".1">'.&mt('Tenths of a Point').'</option>'.
-	    '</select>';
-    
-    $gradeTable .= 
         &build_section_inputs().
 	'<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
 	'<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
@@ -905,14 +938,23 @@ 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="'.$stu_status.'" />'."\n";
+	$gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
     } else {
-	$gradeTable.=&mt('<b>Student Status:</b> [_1]',
-			 &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);')).'<br />';
+        $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status'))
+                      .&Apache::lonhtmlcommon::StatusOptions(
+                           $saveStatus,undef,1,'javascript:reLoadList(this.form);')
+                      .&Apache::lonhtmlcommon::row_closure();
     }
 
-    $gradeTable.=&mt('To '.lc($viewgrade)." a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.").'<br />'."\n".
-	'<input type="hidden" name="command" value="processGroup" />'."\n";
+    $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
+                  .'<input type="checkbox" name="checkPlag" checked="checked" />'
+                  .&Apache::lonhtmlcommon::row_closure(1)
+                  .&Apache::lonhtmlcommon::end_pick_box();
+
+    $gradeTable .= '<p>'
+                  .&mt('To '.lc($viewgrade)." a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.")."\n"
+                  .'<input type="hidden" name="command" value="processGroup" />'
+                  .'</p>';
 
 # checkall buttons
     $gradeTable.=&check_script('gradesub', 'stuinfo');
@@ -920,7 +962,6 @@ LISTJAVASCRIPT
 	'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
 	'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
     $gradeTable.=&check_buttons();
-    $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />'.&mt('Check For Plagiarism').'</label>';
     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
     $gradeTable.= &Apache::loncommon::start_data_table().
 	&Apache::loncommon::start_data_table_header_row();
@@ -1003,7 +1044,7 @@ LISTJAVASCRIPT
 		$gradeTable.= &Apache::loncommon::start_data_table_row();
 	    }
 	    $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
-               '<td align="center"><label><input type=checkbox name="stuinfo" value="'.
+               '<td align="center"><label><input type="checkbox" name="stuinfo" value="'.
                $student.':'.$$fullname{$student}.':::SECTION'.$section.
 	       ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
 	       &nameUserString(undef,$$fullname{$student},$uname,$udom).
@@ -1510,8 +1551,8 @@ INNERJS
     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
     pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />");
 
-    pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
-    pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
+    pDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
+    pDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">');
     pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>");
 }
     function displaySubject(msg,shwsel) {
@@ -1595,8 +1636,8 @@ INNERJS
     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
     hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options<\\/span><\\/h3><br /><br />");
 
-    hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
-    hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
+    hDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
+    hDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">');
     hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>");
   }
 
@@ -2121,13 +2162,13 @@ KEYWORDS
 			    foreach my $file (@$files) {
 			        $file_counter++;
 				&Apache::lonnet::allowuploaded('/adm/grades',$file);
-				$lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';
+				$lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" /> '.$file.'</a>';
 			    }
 			    $lastsubonly.='<br />';
 			}
 			$lastsubonly.='<b>'.&mt('Submitted Answer:').' </b>'.
 			    &cleanRecord($subval,$responsetype,$symb,$partid,
-					 $respid,\%record,$order);
+					 $respid,\%record,$order,undef,$uname,$udom);
 			if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
 			$lastsubonly.='</div>';
 		    }
@@ -3195,19 +3236,19 @@ sub viewgrades {
 	'<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'));
+    my ($common_header,$specific_header);
     if ($env{'form.section'} eq 'all') {
-	$sectionClass=&mt('Class');
+	$common_header = &mt('Assign Common Grade to Class');
+        $specific_header = &mt('Assign Grade to Specific Students in Class');
     } elsif ($env{'form.section'} eq 'none') {
-	$sectionClass=&mt('Students in no Section');
+        $common_header = &mt('Assign Common Grade to Students in no Section');
+	$specific_header = &mt('Assign Grade to Specific Students in no Section');
     } else {
-	$sectionClass=&mt('Students in Section(s) [_1]');
+        my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
+        $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
+	$specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
     }
-    $result.=
-	'<h3>'.
-	&mt("Assign Common Grade to [_1]",$sectionClass,$section_display).'</h3>';
-    $result.= &Apache::loncommon::start_data_table();
+    $result.= '<h3>'.$common_header.'</h3>'.&Apache::loncommon::start_data_table();
     #radio buttons/text box for assigning points for a section or class.
     #handles different parts of a problem
     my ($partlist,$handgrade,$responseType) = &response_type($symb);
@@ -3265,12 +3306,11 @@ sub viewgrades {
 
     #table listing all the students in a section/class
     #header of table
-    $result.= '<h3>'.&mt('Assign Grade to Specific Students in ').$sectionClass,
-			 $section_display.'</h3>';
-    $result.= &Apache::loncommon::start_data_table().
-	&Apache::loncommon::start_data_table_header_row().
-	'<th>'.&mt('No.').'</th>'.
-	'<th>'.&nameUserString('header')."</th>\n";
+    $result.= '<h3>'.$specific_header.'</h3>'.
+              &Apache::loncommon::start_data_table().
+	      &Apache::loncommon::start_data_table_header_row().
+	      '<th>'.&mt('No.').'</th>'.
+	      '<th>'.&nameUserString('header')."</th>\n";
     my (@parts) = sort(&getpartlist($symb));
     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
     my @partids = ();
@@ -3764,7 +3804,7 @@ ENDPICK
 sub csvupload_fields {
     my ($symb) = @_;
     my (@parts) = &getpartlist($symb);
-    my @fields=(['ID','Student ID'],
+    my @fields=(['ID','Student/Employee ID'],
 		['username','Student Username'],
 		['domain','Student Domain']);
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
@@ -4050,7 +4090,7 @@ sub csvuploadassign {
 	   $countdone++;
         }
     }
-    $request->print('<br /><span class="LC_info">'.&mt("Saved [_1] students",$countdone)."</span>\n");
+    $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
     if (@skipped) {
 	$request->print('<p><span class="LC_warning">'.&mt('Skipped Students').'</span></p>');
 	foreach my $student (@skipped) { $request->print("$student<br />\n"); }
@@ -4677,10 +4717,10 @@ Next each scanline is checked for any er
 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
+invalid student/employee 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
+homework problems, either way the student/employee ID is looked up into a
 username:domain.
 
 During the validation phase the instructor can choose to skip scanlines. 
@@ -5154,6 +5194,10 @@ sub scantron_selectphase {
               '<td> '.$format_selector.' </td>'."\n".
               &Apache::loncommon::end_data_table_row()."\n".
               &Apache::loncommon::start_data_table_row()."\n".
+              '<td> '.&mt('Options').' </td>'."\n".
+              '<td> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources').'</label></td>'.
+              &Apache::loncommon::end_data_table_row()."\n".
+              &Apache::loncommon::start_data_table_row()."\n".
               '<td colspan="2">'."\n".
               '<input type="hidden" name="command" value="checksubmissions" />'."\n".
               '<input type="submit" value="'.&mt('Review Scantron Data and Submission Records').'" />'."\n".
@@ -5197,8 +5241,8 @@ sub scantron_selectphase {
       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
+      IDstart     - column where the student/employee ID number starts
+      IDlength    - length of the student/employee ID info
       Qstart      - column where the information from the bubbled
                     'questions' start
       Qlength     - number of columns comprising a single bubble line from
@@ -5258,7 +5302,7 @@ sub get_scantron_config {
 
 =item username_to_idmap
 
-    creates a hash keyed by student id with values of the corresponding
+    creates a hash keyed by student/employee ID with values of the corresponding
     student username:domain.
 
   Arguments:
@@ -5297,7 +5341,7 @@ sub username_to_idmap {
     $whichline         - line number of the passed in scanline
     $field             - type of change to process 
                          (either 
-                          'ID'     -> correct the student ID number
+                          'ID'     -> correct the student/employee ID number
                           'CODE'   -> correct the CODE
                           'answer' -> fixup the submitted answers)
     
@@ -5471,7 +5515,7 @@ sub digits_to_letters {
        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
+       ID  - student/employee ID
        PaperID - if used, the ID number printed on the sheet when the 
                  paper was scanned
        FirstName - first name from the sheet
@@ -6733,10 +6777,10 @@ sub scantron_get_correction {
 	    if ($closest > 0) {
 		foreach my $testcode (@{$closest}) {
 		    my $checked='';
-		    if (!$i) { $checked=' checked="checked" '; }
+		    if (!$i) { $checked=' checked="checked"'; }
 		    $r->print("
    <label>
-       <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked />
+       <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i'$checked />
        ".&mt("Use the similar CODE [_1] instead.",
 	    "<b><tt>".$testcode."</tt></b>")."
     </label>
@@ -6747,10 +6791,10 @@ sub scantron_get_correction {
 	    }
 	}
 	if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
-	    my $checked; if (!$i) { $checked=' checked="checked" '; }
+	    my $checked; if (!$i) { $checked=' checked="checked"'; }
 	    $r->print("
     <label>
-        <input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked />
+        <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked />
        ".&mt("Use the CODE [_1] that is was on the paper, ignoring the error.",
 	     "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
     </label>");
@@ -6781,7 +6825,7 @@ ENDSCRIPT
        ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
 	     "<a target='_blank' href='$href'>","</a>")."
     </label> 
-    ".&mt("Selected CODE is [_1]","<input readonly='true' type='text' size='8' name='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />"));
+    ".&mt("Selected CODE is [_1]",'<input readonly="readonly" type="text" size="8" name="scantron_CODE_selectedvalue" onfocus="javascript:change_radio(\'use_found\')" onchange="javascript:change_radio(\'use_found\')" />'));
 	    $r->print("\n<br />");
 	}
 	$r->print("
@@ -7255,8 +7299,8 @@ sub scantron_get_maxbubble {
 
     &Apache::lonxml::clear_problem_counter();
 
-    my $uname       = $env{'form.student'};
-    my $udom        = $env{'form.userdom'};
+    my $uname       = $env{'user.name'};
+    my $udom        = $env{'user.domain'};
     my $cid         = $env{'request.course.id'};
     my $total_lines = 0;
     %bubble_lines_per_response = ();
@@ -7401,7 +7445,23 @@ sub scantron_process_students {
     my $navmap=Apache::lonnavmaps::navmap->new();
     my $map=$navmap->getResourceByUrl($sequence);
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
-#    $r->print("geto ".scalar(@resources)."<br />");
+    my (%grader_partids_by_symb,%grader_randomlists_by_symb);
+    &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+                            \%grader_randomlists_by_symb);
+    foreach my $resource (@resources) {
+        my $ressymb = $resource->symb();
+        my ($analysis,$parts) =
+            &scantron_partids_tograde($resource,$env{'request.course.id'},
+                                      $env{'user.name'},$env{'user.domain'},1);
+        $grader_partids_by_symb{$ressymb} = $parts;
+        if (ref($analysis) eq 'HASH') {
+            if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
+                $grader_randomlists_by_symb{$ressymb} = 
+                    $analysis->{'parts_withrandomlist'};
+            }
+        }
+    }
+
     my ($uname,$udom);
     my $result= <<SCANTRONFORM;
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
@@ -7469,8 +7529,14 @@ SCANTRONFORM
         my %partids_by_symb;
         foreach my $resource (@resources) {
             my $ressymb = $resource->symb();
-            my ($analysis,$parts) =
-                &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom);            $partids_by_symb{$ressymb} = $parts;
+            if ((exists($grader_randomlists_by_symb{$ressymb})) ||
+                (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
+                my ($analysis,$parts) =
+                    &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom);
+                $partids_by_symb{$ressymb} = $parts;
+            } else {
+                $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
+            }
         }
 
 	&Apache::lonxml::clear_problem_counter();
@@ -7581,6 +7647,27 @@ SCANTRONFORM
     return '';
 }
 
+sub graders_resources_pass {
+    my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb) = @_;
+    if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) && 
+        (ref($grader_randomlists_by_symb) eq 'HASH')) {
+        foreach my $resource (@{$resources}) {
+            my $ressymb = $resource->symb();
+            my ($analysis,$parts) =
+                &scantron_partids_tograde($resource,$env{'request.course.id'},
+                                          $env{'user.name'},$env{'user.domain'},1);
+            $grader_partids_by_symb->{$ressymb} = $parts;
+            if (ref($analysis) eq 'HASH') {
+                if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
+                    $grader_randomlists_by_symb->{$ressymb} =
+                        $analysis->{'parts_withrandomlist'};
+                }
+            }
+        }
+    }
+    return;
+}
+
 sub grade_student_bubbles {
     my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts) = @_;
     if (ref($resources) eq 'ARRAY') {
@@ -7614,12 +7701,14 @@ sub grade_student_bubbles {
 
 sub scantron_upload_scantron_data {
     my ($r)=@_;
-    $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
+    my $dom = $env{'request.role.domain'};
+    my $domdesc = &Apache::lonnet::domain($dom,'description');
+    $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
 							  'domainid',
-							  'coursename');
-    my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},
-						   'domainid');
+							  'coursename',$dom);
+    my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'.
+                       ('&nbsp'x2).&mt('(shows course personnel)'); 
     my $default_form_data=&defaultFormData(&get_symb($r,1));
     $r->print('
 <script type="text/javascript" language="javascript">
@@ -7628,23 +7717,48 @@ sub scantron_upload_scantron_data {
 	    alert("'.&mt('Please use the browse button to select a file from your local directory.').'");
 	    return false;
 	}
+        if (formname.courseid.value == "") {
+            alert("'.&mt('Please use the \"Select Course\" link to open a separate window where you can search for a course to which a file can be uploaded.').'");
+            return false;
+        }
 	formname.submit();
     }
+
+    function ToSyllabus() {
+        var cdom = '."'$dom'".';
+        var cnum = document.rules.courseid.value;
+        if (cdom == "" || cdom == null) {
+            return;
+        }
+        if (cnum == "" || cnum == null) {
+           return;
+        }
+        syllwin=window.open("/public/"+cdom+"/"+cnum+"/syllabus","LONCAPASyllabus",
+                            "height=350,width=350,scrollbars=yes,menubar=no");
+        return;
+    }
+
 </script>
 
+<h3>'.&mt('Send scanned bubblesheet data to a course').'</h3>
+
 <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
-'.$default_form_data.'
-<table>
-<tr><td>'.$select_link.'                             </td></tr>
-<tr><td>'.&mt('Course ID:').'     </td>
-    <td><input name="courseid"   type="text" />      </td></tr>
-<tr><td>'.&mt('Course Name:').'   </td>
-    <td><input name="coursename" type="text" />      </td></tr>
-<tr><td>'.&mt('Domain:').'        </td>
-    <td>'.$domsel.'                                  </td></tr>
-<tr><td>'.&mt('File to upload:').'</td>
-    <td><input type="file" name="upfile" size="50" /></td></tr>
-</table>
+'.$default_form_data.
+  &Apache::lonhtmlcommon::start_pick_box().
+  &Apache::lonhtmlcommon::row_title(&mt('Course ID')).
+  '<input name="courseid" type="text" size="30" />'.$select_link.
+  &Apache::lonhtmlcommon::row_closure().
+  &Apache::lonhtmlcommon::row_title(&mt('Course Name')).
+  '<input name="coursename" type="text" size="30" />'.$syllabuslink.
+  &Apache::lonhtmlcommon::row_closure().
+  &Apache::lonhtmlcommon::row_title(&mt('Domain')).
+  '<input name="domainid" type="hidden" />'.$domdesc.
+  &Apache::lonhtmlcommon::row_closure().
+  &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
+  '<input type="file" name="upfile" size="50" />'.
+  &Apache::lonhtmlcommon::row_closure(1).
+  &Apache::lonhtmlcommon::end_pick_box().'<br />
+
 <input name="command" value="scantronupload_save" type="hidden" />
 <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Scantron Data').'" />
 </form>
@@ -7673,36 +7787,25 @@ sub scantron_upload_scantron_data_save {
 	return '';
     }
     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
-    $r->print(&mt("Doing upload to [_1]",$coursedata{'description'})." <br />");
-    my $fname=$env{'form.upfile.filename'};
-    #FIXME
-    #copied from lonnet::userfileupload()
-    #make that function able to target a specified course
-    # Replace Windows backslashes by forward slashes
-    $fname=~s/\\/\//g;
-    # Get rid of everything but the actual filename
-    $fname=~s/^.*\/([^\/]+)$/$1/;
-    # Replace spaces by underscores
-    $fname=~s/\s+/\_/g;
-    # Replace all other weird characters by nothing
-    $fname=~s/[^\w\.\-]//g;
-    # See if there is anything left
-    unless ($fname) { return 'error: no uploaded file'; }
-    my $uploadedfile=$fname;
-    $fname='scantron_orig_'.$fname;
+    my $uploadedfile;
+    $r->print('<h3>'.&mt("Uploading file to [_1]",$coursedata{'description'}).'</h3>');
     if (length($env{'form.upfile'}) < 2) {
-	$r->print(&mt("<span class=\"LC_error\">Error:</span> The file you attempted to upload, [_1]  contained no information. Please check that you entered the correct filename.",'<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</span>"));
+        $r->print(&mt('[_1]Error:[_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.','<span class="LC_error">','</span>','<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
     } else {
-	my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);
-	if ($result =~ m|^/uploaded/|) {
-	    $r->print(&mt("<span class=\"LC_success\">Success:</span> Successfully uploaded [_1] bytes of data into location [_2]",
-			  (length($env{'form.upfile'})-1),
-			  '<span class="LC_filename">'.$result."</span>"));
+        my $result = 
+            &Apache::lonnet::userfileupload('upfile','','scantron','','','',
+                                            $env{'form.courseid'},$env{'form.domainid'});
+	if ($result =~ m{^/uploaded/}) {
+	    $r->print(&mt('[_1]Success:[_2] Successfully uploaded [_3] bytes of data into location: [_4]',
+                          '<span class="LC_success">','</span>',(length($env{'form.upfile'})-1),
+			  '<span class="LC_filename">'.$result.'</span>'));
+            ($uploadedfile) = ($result =~ m{/([^/]+)$});
+            $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
+                                                       $env{'form.courseid'},$uploadedfile));
 	} else {
-	    $r->print(&mt("<span class=\"LC_error\">Error:</span> An error ([_1]) occurred when attempting to upload the file, [_2]",
-			  $result,
-			  '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</span>"));
-
+	    $r->print(&mt('[_1]Error:[_2] An error ([_3]) occurred when attempting to upload the file, [_4]',
+                          '<span class="LC_error">','</span>',$result,
+			  '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
 	}
     }
     if ($symb) {
@@ -7713,6 +7816,92 @@ sub scantron_upload_scantron_data_save {
     return '';
 }
 
+sub validate_uploaded_scantron_file {
+    my ($cdom,$cname,$fname) = @_;
+    my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname);
+    my @lines;
+    if ($scanlines ne '-1') {
+        @lines=split("\n",$scanlines,-1);
+    }
+    my $output;
+    if (@lines) {
+        my (%counts,$max_match_format);
+        my ($max_match_count,$max_match_pct) = (0,0);
+        my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
+        my %idmap = &username_to_idmap($classlist);
+        foreach my $key (keys(%idmap)) {
+            my $lckey = lc($key);
+            $idmap{$lckey} = $idmap{$key};
+        }
+        my %unique_formats;
+        my @formatlines = &get_scantronformat_file();
+        foreach my $line (@formatlines) {
+            chomp($line);
+            my @config = split(/:/,$line);
+            my $idstart = $config[5];
+            my $idlength = $config[6];
+            if (($idstart ne '') && ($idlength > 0)) {
+                if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') {
+                    push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]); 
+                } else {
+                    $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]];
+                }
+            }
+        }
+        foreach my $key (keys(%unique_formats)) {
+            my ($idstart,$idlength) = split(':',$key);
+            %{$counts{$key}} = (
+                               'found'   => 0,
+                               'total'   => 0,
+                              );
+            foreach my $line (@lines) {
+                next if ($line =~ /^#/);
+                next if ($line =~ /^[\s\cz]*$/);
+                my $id = substr($line,$idstart-1,$idlength);
+                $id = lc($id);
+                if (exists($idmap{$id})) {
+                    $counts{$key}{'found'} ++;
+                }
+                $counts{$key}{'total'} ++;
+            }
+            if ($counts{$key}{'total'}) {
+                my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'});
+                if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
+                    $max_match_pct = $percent_match;
+                    $max_match_format = $key;
+                    $max_match_count = $counts{$key}{'total'};
+                }
+            }
+        }
+        if (ref($unique_formats{$max_match_format}) eq 'ARRAY') {
+            my $format_descs;
+            my $numwithformat = @{$unique_formats{$max_match_format}};
+            for (my $i=0; $i<$numwithformat; $i++) {
+                my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]);
+                if ($i<$numwithformat-2) {
+                    $format_descs .= '"<i>'.$desc.'</i>", ';
+                } elsif ($i==$numwithformat-2) {
+                    $format_descs .= '"<i>'.$desc.'</i>" '.&mt('and').' ';
+                } elsif ($i==$numwithformat-1) {
+                    $format_descs .= '"<i>'.$desc.'</i>"';
+                }
+            }
+            my $showpct = sprintf("%.0f",$max_match_pct).'%';
+            $output .= '<br />'.&mt('Comparison of student IDs in the uploaded file with the course roster found matches for [_1] of the [_2] entries in the file (for the format defined for [_3]).','<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
+                       '<br />'.&mt('A low percentage of matches results from one of the following:').'<ul>'.
+                       '<li>'.&mt('The file was uploaded to the wrong course').'</li>'.
+                       '<li>'.&mt('The data are not in the format expected for the domain: [_1]',
+                                  '<i>'.$cdom.'</i>').'</li>'.
+                       '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
+                       '<li>'.&mt('The course roster is not up to date').'</li>'.
+                       '</ul>';
+        }
+    } else {
+        $output = '<span class="LC_warning">'.&mt('Uploaded file contained no data').'</span>';
+    }
+    return $output;
+}
+
 sub valid_file {
     my ($requested_file)=@_;
     foreach my $filename (sort(&scantron_filenames())) {
@@ -7779,7 +7968,10 @@ sub checkscantron_results {
     my %idmap=&Apache::grades::username_to_idmap($classlist);
     my $navmap=Apache::lonnavmaps::navmap->new();
     my $map=$navmap->getResourceByUrl($sequence);
-    my @resources=$navmap->retrieveResources($map,undef,1,0);
+    my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+    my (%grader_partids_by_symb,%grader_randomlists_by_symb);
+    &graders_resources_pass(\@resources,\%grader_partids_by_symb,                             \%grader_randomlists_by_symb);
+
     my ($uname,$udom);
     my (%scandata,%lastname,%bylast);
     $r->print('
@@ -7794,7 +7986,7 @@ sub checkscantron_results {
                                     'inline',undef,'checkscantron');
     my ($username,$domain,$started);
 
-    &Apache::grades::scantron_get_maxbubble();  # Need the bubble lines array to parse.
+    &scantron_get_maxbubble();  # Need the bubble lines array to parse.
 
     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
                                           'Processing first student');
@@ -7835,9 +8027,15 @@ sub checkscantron_results {
         ($username,$domain)=split(/:/,$uname);
         my $counter = -1;
         foreach my $resource (@resources) {
+            my $parts;
             my $ressymb = $resource->symb();
-            my ($analysis,$parts) =
-                &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain);
+            if ((exists($grader_randomlists_by_symb{$ressymb})) ||
+                (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
+                (my $analysis,$parts) =
+                    &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain);
+            } else {
+                $parts = $grader_partids_by_symb{$ressymb};
+            }
             ($counter,my $recording) =
                 &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
                                          $scandata{$pid},$parts,
@@ -8459,7 +8657,7 @@ sub process_clicker {
     my %checked;
     foreach my $gradingmechanism ('attendance','personnel','specific','given') {
        if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
-          $checked{$gradingmechanism}="checked='checked'";
+          $checked{$gradingmechanism}=' checked="checked"';
        }
     }
 
@@ -8523,11 +8721,11 @@ 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><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>
+<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" />
-<br /><label><input type="radio" name="gradingmechanism" value="given" $checked{'given'} onClick="sanitycheck()" />$given </label>
+<br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onClick="sanitycheck()" />$given </label>
 <br />&nbsp;&nbsp;&nbsp;
 <input type="text" name="givenanswer" size="50" />
 <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
@@ -9006,7 +9204,7 @@ sub handler {
         } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) {
             $request->print(&checkscantron_results($request));     
 	} elsif ($command) {
-	    $request->print("Access Denied ($command)");
+	    $request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
 	}
     }
     if ($ssi_error) {
@@ -9174,7 +9372,7 @@ ssi_with_retries()
 =item  scantron_validate_ID() : 
 
    Validates all scanlines in the selected file to not have any
-   invalid or underspecified student IDs
+   invalid or underspecified student/employee IDs
 
 =back