--- loncom/homework/grades.pm	2004/02/25 00:31:57	1.178
+++ loncom/homework/grades.pm	2004/04/24 08:35:10	1.189
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.178 2004/02/25 00:31:57 albertel Exp $
+# $Id: grades.pm,v 1.189 2004/04/24 08:35:10 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -511,7 +511,7 @@ sub verifyreceipt {
     my $request  = shift;
 
     my $courseid = $ENV{'request.course.id'};
-    my $receipt  = unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'.
+    my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
 	$ENV{'form.receipt'};
     $receipt     =~ s/[^\-\d]//g;
     my $url      = $ENV{'form.url'};
@@ -1295,10 +1295,10 @@ sub gradeBox {
     my $ctr = 0;
     $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
     while ($ctr<=$wgt) {
-	$result.= '<td><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
+	$result.= '<td><nobr><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
 	    'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
 	    $ctr.')" value="'.$ctr.'" '.
-	    ($score eq $ctr ? 'checked':'').' /> '.$ctr."</td>\n";
+	    ($score eq $ctr ? 'checked':'').' /> '.$ctr."</nobr></td>\n";
 	$result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
 	$ctr++;
     }
@@ -1704,8 +1704,10 @@ KEYWORDS
 	    ($ENV{'form.command'} eq 'processGroup' && $counter == $total)) {
 	    $toGrade.='</form>'.&show_grading_menu_form($symb,$url) 
 	}
-	$request = print($toGrade);
+	$request->print($toGrade);
 	return;
+    } else {
+	$request->print('</td></tr></table></td></tr></table>'."\n");
     }
 
     # essay grading message center
@@ -3443,6 +3445,30 @@ sub scantron_scantab {
     return $result;
 }
 
+sub scantron_CODElist {
+    my $cdom = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'};
+    my $cnum = $ENV{'course.'.$ENV{'request.course.id'}.'.num'};
+    my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
+    my $namechoice='<option></option>';
+    foreach my $name (@names) {
+	$namechoice.='<option value="'.$name.'">'.$name.'</option>';
+    }
+    $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
+    return $namechoice;
+}
+
+sub scantron_CODEunique {
+    my $result='<nobr>
+                 <input type="radio" name="scantron_CODEunique"
+                        value="Yes" checked="on" /> Yes
+                </nobr>
+                <nobr>
+                 <input type="radio" name="scantron_CODEunique"
+                        value="No" /> No
+                </nobr>';
+    return $result;
+}
+
 sub scantron_selectphase {
     my ($r) = @_;
     my ($symb,$url)=&get_symb_and_url($r);
@@ -3452,6 +3478,8 @@ sub scantron_selectphase {
     my $grading_menu_button=&show_grading_menu_form($symb,$url);
     my $file_selector=&scantron_uploads();
     my $format_selector=&scantron_scantab();
+    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,
@@ -3459,7 +3487,7 @@ sub scantron_selectphase {
     <table width="100%" border="0">
     <tr>
       <td bgcolor="#777777">
-       <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantro_process">
+       <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
        <input type="hidden" name="command" value="scantron_validate" />
         $default_form_data
         <table width="100%" border="0">
@@ -3478,6 +3506,12 @@ sub scantron_selectphase {
             <td> Format of data file: </td><td> $format_selector </td>
           </tr>
           <tr bgcolor="#ffffe6">
+            <td> Saved CODEs to validate against: </td><td> $CODE_selector</td>
+          </tr>
+          <tr bgcolor="#ffffe6">
+            <td> Each CODE is only to be used once:</td><td> $CODE_unique </td>
+          </tr>
+          <tr bgcolor="#ffffe6">
             <td>
 <!-- FIXME this is lazy, a single parse of the set should let me know what this is -->
               Last line to expect an answer on: </td><td>
@@ -3485,6 +3519,13 @@ sub scantron_selectphase {
 	    </td>
           </tr>
           <tr bgcolor="#ffffe6">
+	    <td> Options: </td>
+            <td>
+                <input type="checkbox" name="scantron_options" value="redo_skipped"/> Redo skipped records <br />
+                <input type="checkbox" name="scantron_options" value="ignore_corrections"/> Ignore Original Corrections
+	    </td>
+          </tr>
+          <tr bgcolor="#ffffe6">
             <td colspan="2">
               <input type="submit" value="Validate Scantron Records" />
             </td>
@@ -3545,6 +3586,40 @@ UPLOAD
     </tr>
 SCANTRONFORM
     }
+    $r->print(<<SCANTRONFORM);
+    <tr>
+      <td bgcolor="#777777">
+        <form action='/adm/grades' name='scantron_download'>
+          <input type="hidden" name="command" value="scantron_download" />
+          <table width="100%" border="0">
+            <tr bgcolor="#e6ffff">
+              <td colspan="2">
+                &nbsp;<b>Download a scoring office file</b>
+              </td>
+            </tr>
+            <tr bgcolor="#ffffe6">
+              <td> Filename of scoring office file: </td><td> $file_selector </td>
+            </tr>
+            <tr bgcolor="#ffffe6">
+	      <td>
+                Records to download
+              </td>
+              <td>
+                  <input type="radio" name="scantron_options" value="download_skipped"/> Skipped Records <br />
+                  <input type="radio" name="scantron_options" value="download_corrected"/> Corrected Records <br />
+                  <input checked="on" type="radio" name="scantron_options" value="dowload_orig"/> Original Records
+              </td>
+            </tr>
+            <tr bgcolor="#ffffe6">
+              <td colspan="2">
+                <input type="submit" value="Validate Scantron Records" />
+              </td>
+            </tr>
+          </table>
+        </form>
+      </td>
+    </tr>
+SCANTRONFORM
 
     $r->print(<<SCANTRONFORM);
   </table>
@@ -3601,7 +3676,7 @@ 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 to large');
+	    return ($line,1,'New value too large');
 	}
 	if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
 	    $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
@@ -3613,6 +3688,19 @@ sub scantron_fixup_scanline {
 	    &scan_data($scan_data,"$whichline.user",
 		       $args->{'username'}.':'.$args->{'domain'});
 	}
+    } elsif ($field eq 'CODE') {
+	if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
+	    return ($line,1,'New CODE value too large');
+	}
+	if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
+	    $args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',
+				       $args->{'CODE'});
+	}
+	substr($line,$$scantron_config{'CODEstart'}-1,
+	       $$scantron_config{'CODElength'})=$args->{'CODE'};
+	if ($args->{'CODE'}=~/^\s*$/) {
+	    &scan_data($scan_data,"$whichline.CODE",$args->{'CODE'});
+	}
     } elsif ($field eq 'answer') {
 	my $length=$scantron_config->{'Qlength'};
 	my $off=$scantron_config->{'Qoff'};
@@ -3643,7 +3731,7 @@ sub scan_data {
 }
 
 sub scantron_parse_scanline {
-    my ($line,$whichline,$scantron_config,$scan_data)=@_;
+    my ($line,$whichline,$scantron_config,$scan_data,$justCODE)=@_;
     my %record;
     my $questions=substr($line,$$scantron_config{'Qstart'}-1);
     my $data=substr($line,0,$$scantron_config{'Qstart'}-1);
@@ -3655,6 +3743,7 @@ sub scantron_parse_scanline {
 	    #FIXME interpret first N questions
 	}
     }
+    if ($justCODE) { return \%record; }
     $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
 				  $$scantron_config{'IDlength'});
     $record{'scantron.PaperID'}=
@@ -3746,6 +3835,11 @@ sub scantron_process_corrections {
 				     'ID',{'newid'=>$newid,
 				    'username'=>$ENV{'form.scantron_username'},
 				    'domain'=>$ENV{'form.scantron_domain'}});
+    } elsif ($ENV{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
+	my $newCODE=$ENV{'form.scantron_CODE'};
+	($line,$err,$errmsg)=
+	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
+				     'CODE',{'CODE'=>$newCODE});
     } elsif ($ENV{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
 	foreach my $question (split(',',$ENV{'form.scantron_questions'})) {
 	    ($line,$err,$errmsg)=
@@ -3939,7 +4033,7 @@ sub scantron_validate_ID {
 					 $line,'duplicateID',$username);
 		return(1);
 	    }
-	    #FIXME store away line we prviously saw the ID on to use above
+	    #FIXME store away line we previously saw the ID on to use above
 	    $found{'ids'}{$found}++;
 	    $found{'usernames'}{$username}++;
 	} else {
@@ -3986,7 +4080,7 @@ sub scantron_get_correction {
     $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
     if ($error =~ /ID$/) {
-	if ($error eq 'unknownID') {
+	if ($error eq 'incorrectID') {
 	    $r->print("The encoded ID is not in the classlist</p>\n");
 	} elsif ($error eq 'duplicateID') {
 	    $r->print("The encoded ID has also been used by a previous paper $arg</p>\n");
@@ -4004,9 +4098,47 @@ sub scantron_get_correction {
 				       'scantron_username','scantron_domain'));
 	$r->print(": <input type='text' name='scantron_username' value='' />");
 	$r->print("\n@".
-		 &Apache::loncommon::select_dom_form($ENV{'request.role..domain'},'scantron_domain'));
+		 &Apache::loncommon::select_dom_form($ENV{'request.role.domain'},'scantron_domain'));
 
 	$r->print('</li>');
+    } elsif ($error =~ /CODE$/) {
+	if ($error eq 'incorrectCODE') {
+	    $r->print("</p><p>The encoded CODE is not in the list of possible CODEs</p>\n");
+	} elsif ($error eq 'duplicateCODE') {
+	    $r->print("</p><p>The encoded CODE has also been used by a previous paper $arg, and CODEs were supposed to be unique</p>\n");
+	}
+	$r->print("<p>The CODE on the form is  <tt>".
+		  $$scan_record{'scantron.CODE'}."</tt><br />\n");
+	$r->print("<p>The ID on the form is  <tt>".
+		  $$scan_record{'scantron.ID'}."</tt><br />\n");
+	$r->print("The name on the paper is ".
+		  $$scan_record{'scantron.LastName'}.",".
+		  $$scan_record{'scantron.FirstName'}."</p>");
+	$r->print("<p>How should I handle this? <br /> \n");
+	$r->print("\n<br /> ");
+	$r->print("<input type='radio' name='scan_CODE_resolution' value='use_unfound' checked='on' /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.");
+	$r->print("\n<br />");
+	$r->print(<<ENDSCRIPT);
+<script type="text/javascript">
+function change_radio(field) {
+    var slct=document.scantronupload.scan_CODE_resolution;
+    var i;
+    for (i=0;i<slct.length;i++) {
+        if (slct[i].value==field) { slct[i].checked=true; }
+    }
+}
+</script>
+ENDSCRIPT
+	my $href="/adm/pickcode?".
+	   "form=".&Apache::lonnet::escape("scantronupload").
+	   "&scantron_format=".&Apache::lonnet::escape($ENV{'form.scantron_format'}).
+	   "&scantron_CODElist=".&Apache::lonnet::escape($ENV{'form.scantron_CODElist'}).
+	   "&curCODE=".&Apache::lonnet::escape($$scan_record{'scantron.CODE'}).
+	   "&scantron_selectfile=".&Apache::lonnet::escape($ENV{'form.scantron_selectfile'});
+	$r->print("<input type='radio' name='scan_CODE_resolution' value='use_found' /> <a target='_blank' href='$href'>Select</a> a CODE from the list of all CODEs and use it. Selected CODE is <input readonly='true' type='text' size='8' name='scan_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />");
+	$r->print("\n<br />");
+	$r->print("<input type='radio' name='scan_CODE_resolution' value='use_typed' /> Use <input type='text' size='8' name='scan_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" /> as the CODE.");
+	$r->print("\n<br /><br />");
     } elsif ($error eq 'doublebubble') {
 #FIXME Need to print out who this is along with the paper info
 	$r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n");
@@ -4058,6 +4190,48 @@ sub scantron_bubble_selector {
 sub scantron_validate_CODE {
     my ($r,$currentphase) = @_;
     #FIXME doesn't do anything yet
+    my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'});
+    if ($scantron_config{'CODElocation'} &&
+	$scantron_config{'CODEstart'} &&
+	$scantron_config{'CODElength'}) {
+	if (!$ENV{'form.scantron_CODElist'}) {
+	    &FIXME_blow_up()
+	}
+    } else {
+	&Apache::lonnet::logthis(" CODE stuf $scantron_config{'CODElocation'}:$scantron_config{'CODEstart'}:$scantron_config{'CODElength'}");
+	return (0,$currentphase+1);
+    }
+    
+    my %usedCODEs;
+
+    my $old_name=$ENV{'form.scantron_CODElist'};
+    my $cdom =$ENV{'course.'.$ENV{'request.course.id'}.'.domain'};
+    my $cnum =$ENV{'course.'.$ENV{'request.course.id'}.'.num'};
+    my %result=&Apache::lonnet::get('CODEs',[$old_name],$cdom,$cnum);
+    my %allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
+
+    my ($scanlines,$scan_data)=&scantron_getfile();
+    for (my $i=0;$i<=$scanlines->{'count'};$i++) {
+	my $line=&scantron_get_line($scanlines,$i);
+	if ($line=~/^[\s\cz]*$/) { next; }
+	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
+						 $scan_data);
+	my $CODE=$$scan_record{'scantron.CODE'};
+	my $error=0;
+	if (!exists($allcodes{$CODE})) {
+	    &scantron_get_correction($r,$i,$scan_record,
+				     \%scantron_config,
+				     $line,'incorrectCODE',$CODE);
+	    return(1);
+	}
+	if (exists($usedCODEs{$CODE}) && $ENV{'form.scantron_CODEunique'}) {
+	    &scantron_get_correction($r,$i,$scan_record,
+				     \%scantron_config,
+				     $line,'duplicateCODE',$CODE);
+	    return(1);
+	}
+	$usedCODEs{$CODE}++;
+    }
     return (0,$currentphase+1);
 }
 
@@ -4202,7 +4376,8 @@ sub scantron_upload_scantron_data {
     my ($r)=@_;
     $r->print(&Apache::loncommon::coursebrowser_javascript($ENV{'request.role.domain'}));
     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
-							  'domainid');
+							  'domainid',
+							  'coursename');
     my $domsel=&Apache::loncommon::select_dom_form($ENV{'request.role.domain'},
 						   'domainid');
     my $default_form_data=&defaultFormData(&get_symb_and_url($r,1));
@@ -4219,12 +4394,14 @@ sub scantron_upload_scantron_data {
 
 <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
 $default_form_data
-Course: <input name='courseid' type='text' />
-Domain: $domsel $select_link
-<br />
+<table>
+<tr><td>$select_link </td></tr>
+<tr><td>Course ID:   </td><td><input name='courseid' type='text' />  </td></tr>
+<tr><td>Course Name: </td><td><input name='coursename' type='text' /></td></tr>
+<tr><td>Domain:      </td><td>$domsel                                </td></tr>
+<tr><td>File to upload:</td><td><input type="file" name="upfile" size="50" /></td></tr>
+</table>
 <input name='command' value='scantronupload_save' type='hidden' />
-File to upload:<input type="file" name="upfile" size="50" />
-<br />
 <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />
 </form>
 UPLOAD
@@ -4233,11 +4410,21 @@ UPLOAD
 
 sub scantron_upload_scantron_data_save {
     my($r)=@_;
+    my ($symb,$url)=&get_symb_and_url($r,1);
+    my $doanotherupload=
+	'<br /><form action="/adm/grades" method="post">'."\n".
+	'<input type="hidden" name="command" value="scantronupload" />'."\n".
+	'<input type="submit" name="submit" value="Do Another Upload" />'."\n".
+	'</form>'."\n";
     if (!&Apache::lonnet::allowed('usc',$ENV{'form.domainid'}) &&
 	!&Apache::lonnet::allowed('usc',
 			    $ENV{'form.domainid'}.'_'.$ENV{'form.courseid'})) {
 	$r->print("You are not allowed to upload Scantron data to the requested course.<br />");
-	$r->print(&show_grading_menu_form(&get_symb_and_url($r)));
+	if ($symb) {
+	    $r->print(&show_grading_menu_form($symb,$url));
+	} else {
+	    $r->print($doanotherupload);
+	}
 	return '';
     }
     $r->print("Doing upload to ".$ENV{'form.courseid'}." <br />");
@@ -4258,17 +4445,20 @@ sub scantron_upload_scantron_data_save {
     # See if there is anything left
     unless ($fname) { return 'error: no uploaded file'; }
     $fname='scantron_orig_'.$fname;
-    $r->print(&Apache::lonnet::finishuserfileupload($ENV{'form.courseid'},
-						    $ENV{'form.domainid'},
-						    $home,'upfile',$fname));
-    my ($symb,$url)=&get_symb_and_url($r);
+    if (length($ENV{'form.upfile'}) < 2) {
+	$r->print("<font color='red'>Error:</font> 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.");
+    } else {
+	my $result=&Apache::lonnet::finishuserfileupload($ENV{'form.courseid'},$ENV{'form.domainid'},$home,'upfile',$fname);
+	if ($result =~ m|^/uploaded/|) {
+	    $r->print("<font color='green'>Success:</font> Successfully uploaded ".(length($ENV{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");
+	} else {
+	    $r->print("<font color='red'>Error:</font> An error (".$result.") occured when attempting to upload the file, <tt>".&HTML::Entities::encode($ENV{'form.upfile.filename'},'<>&"')."</tt>");
+	}
+    }
     if ($symb) {
-	$r->print(&show_grading_menu_form(&get_symb_and_url($r)));
+	$r->print(&show_grading_menu_form($symb,$url));
     } else {
-	$r->print('<br /><form action="/adm/grades" method="post">'."\n".
-		  '<input type="hidden" name="command" value="scantronupload" />'."\n".
-		  '<input type="submit" name="submit" value="Do Another Upload" />'."\n".
-		  '</form>'."\n");
+	$r->print($doanotherupload);
     }
     return '';
 }
@@ -4423,17 +4613,18 @@ GRADINGMENUJS
 
     $result.='<table width="100%" border=0>';
     $result.='<tr bgcolor="#ffffe6"><td>'.
-	'<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="Upload" />'.
-	' scores from file </td></tr>'."\n";
+	'<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.
+	' '.&mt('scores from 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="Grade" /> scantron forms</td></tr>'."\n";
+	'" 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="Verify" />'.
-	    ' submission Receipt no: '.unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).
+	    '<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";
     }