--- loncom/homework/grades.pm	2005/05/26 21:15:04	1.267
+++ loncom/homework/grades.pm	2005/08/12 21:33:41	1.278
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.267 2005/05/26 21:15:04 albertel Exp $
+# $Id: grades.pm,v 1.278 2005/08/12 21:33:41 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -152,6 +152,7 @@ sub get_display_part {
     }
     return $display;
 }
+
 #--- Show resource title
 #--- and parts and response type
 sub showResourceInfo {
@@ -284,6 +285,11 @@ sub cleanRecord {
 	}
 	$answer =~ s-\n-<br />-g;
 	return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
+    } elsif ( $response eq 'organic') {
+	my $result='Smile representation: "<tt>'.$answer.'</tt>"';
+	my $jme=$record->{$version."resource.$partid.$respid.molecule"};
+	$result.=&Apache::chemresponse::jme_img($jme,$answer,400);
+	return $result;
     }
     return $answer;
 }
@@ -737,7 +743,7 @@ LISTJAVASCRIPT
 	    if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
 		foreach (sort keys(%status)) {
 		    next if (/^resource.*?submitted_by$/);
-		    $gradeTable.='<td align="middle">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";
+		    $gradeTable.='<td align="center">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";
 		}
 	    }
 #	    $gradeTable.='<td></td>' if ($ctr%2 ==1);
@@ -1346,6 +1352,14 @@ sub gradeBox {
     my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
 
     my $display_part=&get_display_part($partid,undef,$symb);
+
+    my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
+				       [$partid]);
+    my $aggtries = $$record{'resource.'.$partid.'.tries'};
+    if ($last_resets{$partid}) {
+        $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
+    }
+
     $result.='<table border="0"><tr><td>'.
 	'<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";
 
@@ -1384,7 +1398,11 @@ sub gradeBox {
     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
 	'<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
 	'<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
-	$$record{'resource.'.$partid.'.solved'}.'" />'."\n";
+	$$record{'resource.'.$partid.'.solved'}.'" />'."\n".
+        '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
+        $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
+        '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
+        $aggtries.'" />'."\n";
     $result.='</td></tr></table>'."\n";
     return $result;
 }
@@ -1593,7 +1611,6 @@ KEYWORDS
     }
 
     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
-
     my ($partlist,$handgrade,$responseType) = &response_type($url,$symb);
 
     # Display student info
@@ -2117,6 +2134,8 @@ sub saveHandGrade {
     my @parts_graded;
     my %newrecord  = ();
     my ($pts,$wgt) = ('','');
+    my %aggregate = ();
+    my $aggregateflag = 0;
     foreach my $new_part (split(/:/,$env{'form.partlist'.$newflg})) {
 	#collaborator may vary for different parts
 	if ($submitter && $new_part ne $part) { next; }
@@ -2136,6 +2155,21 @@ sub saveHandGrade {
 	    }
 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 		"$env{'user.name'}:$env{'user.domain'}";
+            my $totaltries = $record{'resource.'.$part.'.tries'};
+
+            my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
+					       [$new_part]);
+            my $aggtries =$totaltries;
+            if ($last_resets{$new_part}) {
+                $aggtries = &get_num_tries(\%record,$last_resets{$new_part},
+					   $new_part);
+            }
+
+            my $solvedstatus = $record{'resource.'.$new_part.'.solved'};
+            if ($aggtries > 0) {
+                &decrement($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
+                $aggregateflag = 1;
+            }
 	} elsif ($dropMenu eq '') {
 	    $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 
 		    $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 
@@ -2184,9 +2218,73 @@ sub saveHandGrade {
 	&Apache::lonnet::cstore(\%newrecord,$symb,
 				$env{'request.course.id'},$domain,$stuname);
     }
+    if ($aggregateflag) {
+        &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
+                  $env{'course.'.$env{'request.course.id'}.'.domain'},
+                  $env{'course.'.$env{'request.course.id'}.'.num'});
+    }
     return '',$pts,$wgt;
 }
 
+# ----------- Provides number of tries since last reset.
+sub get_num_tries {
+    my ($record,$last_reset,$part) = @_;
+    my $timestamp = '';
+    my $num_tries = 0;
+    if ($$record{'version'}) {
+        for (my $version=$$record{'version'};$version>=1;$version--) {
+            if (exists($$record{$version.':resource.'.$part.'.solved'})) {
+                $timestamp = $$record{$version.':timestamp'};
+                if ($timestamp > $last_reset) {
+                    $num_tries ++;
+                } else {
+                    last;
+                }
+            }
+        }
+    }
+    return $num_tries;
+}
+
+# ----------- Determine decrements required in aggregate totals 
+sub decrement_aggs {
+    my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
+    my %decrement = (
+                        attempts => 0,
+                        users => 0,
+                        correct => 0
+                    );
+    $decrement{'attempts'} = $aggtries;
+    if ($solvedstatus =~ /^correct/) {
+        $decrement{'correct'} = 1;
+    }
+    if ($aggtries == $totaltries) {
+        $decrement{'users'} = 1;
+    }
+    foreach my $type (keys (%decrement)) {
+        $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
+    }
+    return;
+}
+
+# ----------- Determine timestamps for last reset of aggregate totals for parts  
+sub get_last_resets {
+    my ($symb,$courseid,$partids) =@_;
+    my %last_resets;
+    my $cdom = $env{'course.'.$courseid.'.domain'};
+    my $cname = $env{'course.'.$courseid.'.num'};
+    my @keys;
+    foreach my $part (@{$partids}) {
+	push(@keys,"$symb\0$part\0resettime");
+    }
+    my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys,
+				     $cdom,$cname);
+    foreach my $part (@{$partids}) {
+	$last_resets{$part}=$results{"$symb\0$part\0resettime"};
+    }
+    return %last_resets;
+}
+
 # ----------- Handles creating versions for portfolio files as answers
 sub version_portfiles {
     my ($record, $parts_graded, $courseid, $symb, $domain, $stuname, $v_flag) = @_;
@@ -2195,12 +2293,13 @@ sub version_portfiles {
     my $portfolio_root = &Apache::loncommon::propath($domain,
 						 $stuname).
 						'/userfiles/portfolio';
-    foreach my $key(keys %$record) {
+    foreach my $key (keys(%$record)) {
         my $new_portfiles;
+	
         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
             my @v_portfiles;
             my @portfiles = split(/,/,$$record{$key});
-            # &Apache::lonnet::logthis("should be unmarking and remarking");
+            &Apache::lonnet::logthis("should be unmarking and remarking $key",@portfiles);
             foreach my $file (@portfiles) {
                 &Apache::lonnet::unmark_as_readonly($domain,$stuname,[$symb,$env{'request.course.id'}],$file);
                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*$)/);
@@ -2223,12 +2322,11 @@ sub version_portfiles {
                     }
                 }
                 $version++;
-                my $home_server = &Apache::lonnet::homeserver($stuname,$domain,undef);
                 $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stuname/portfolio$directory$answer_file");
                 if($env{'form.copy'} eq '-1') {
                     &Apache::lonnet::logthis('problem getting file '.$directory.$answer_file);
                 } else {
-                   my $copy_result = &Apache::lonnet::finishuserfileupload($stuname,$domain,$home_server,'copy',
+                   my $copy_result = &Apache::lonnet::finishuserfileupload($stuname,$domain,'copy',
                                     '/portfolio'.$directory.$answer_file_parts[0].'.'.$version.'.'.$answer_file_parts[-1]);
                     push(@v_portfiles, $answer_file_parts[0].'.'.$version.'.'.$answer_file_parts[-1]);
                     &Apache::lonnet::mark_as_readonly($domain,$stuname,
@@ -2376,7 +2474,7 @@ sub viewgrades_js {
 	    if (selval[2].selected) {
 		document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
 	    }
-	}
+        }
     }
 
     function resetEntry(numpart) {
@@ -2509,11 +2607,13 @@ sub viewgrades {
 	'<table border=0><tr bgcolor="#deffff"><td>&nbsp;<b>No.</b>&nbsp;</td>'.
 	'<td>'.&nameUserString('header')."</td>\n";
     my (@parts) = sort(&getpartlist($url,$symb));
+    my @partids = ();
     foreach my $part (@parts) {
 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
 	$display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower
 	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
 	my ($partid) = &split_part_type($part);
+        push(@partids, $partid);
 	my $display_part=&get_display_part($partid,$url,$symb);
 	if ($display =~ /^Partial Credit Factor/) {
 	    $result.='<td><b>Score Part:</b> '.$display_part.
@@ -2527,6 +2627,9 @@ sub viewgrades {
     }
     $result.='</tr>';
 
+    my %last_resets = 
+	&get_last_resets($symb,$env{'request.course.id'},\@partids);
+
     #get info for each student
     #list all the students - with points and grade status
     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
@@ -2534,7 +2637,7 @@ sub viewgrades {
     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
 	$ctr++;
 	$result.=&viewstudentgrade($url,$symb,$env{'request.course.id'},
-				   $_,$$fullname{$_},\@parts,\%weight,$ctr);
+				   $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);
     }
     $result.='</table></td></tr></table>';
     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
@@ -2551,10 +2654,11 @@ sub viewgrades {
 
 #--- call by previous routine to display each student
 sub viewstudentgrade {
-    my ($url,$symb,$courseid,$student,$fullname,$parts,$weight,$ctr) = @_;
+    my ($url,$symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
     my ($uname,$udom) = split(/:/,$student);
     $student=~s/:/_/;
     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
+    my %aggregates = (); 
     my $result='<tr bgcolor="#ffffdd"><td align="right">'.
 	'<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.
 	"\n".$ctr.'&nbsp;</td><td>&nbsp;'.
@@ -2564,7 +2668,22 @@ sub viewstudentgrade {
     foreach my $apart (@$parts) {
 	my ($part,$type) = &split_part_type($apart);
 	my $score=$record{"resource.$part.$type"};
-	$result.='<td align="middle">';
+        $result.='<td align="center">';
+        my ($aggtries,$totaltries);
+        unless (exists($aggregates{$part})) {
+	    $totaltries = $record{'resource.'.$part.'.tries'};
+
+	    $aggtries = $totaltries;
+            if ($$last_resets{$part}) {  
+                $aggtries = &get_num_tries(\%record,$$last_resets{$part},
+					   $part);
+            }
+            $result.='<input type="hidden" name="'.
+                'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n";
+            $result.='<input type="hidden" name="'.
+                'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n";
+            $aggregates{$part} = 1;
+        }
 	if ($type eq 'awarded') {
 	    my $pts = $score eq '' ? '' : $score*$$weight{$part};
 	    $result.='<input type="hidden" name="'.
@@ -2679,12 +2798,13 @@ sub editgrades {
 	    $noupdate.=$line."<td colspan=\"$numcols\"><font color=\"red\">Not allowed to modify student</font></td></tr>";
 	    next;
 	}
+        my %aggregate = ();
+        my $aggregateflag = 0;
 	foreach (@partid) {
 	    my $old_aw    = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
 	    my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
 	    my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;
 	    my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
-
 	    my $awarded   = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
 	    my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
 	    my $partial   = $awarded eq '' ? '' : $pcr;
@@ -2707,6 +2827,13 @@ sub editgrades {
 		$newrecord{'resource.'.$_.'.awarded'} = 0;
 		$newrecord{'resource.'.$_.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
 		$updateflag = 1;
+                if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
+                    my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'};
+                    my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'};
+                    my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'};
+                    &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus);
+                    $aggregateflag = 1;
+                }
 	    } elsif (!($old_part eq $partial && $old_score eq $score)) {
 		$updateflag = 1;
 		$newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
@@ -2746,6 +2873,11 @@ sub editgrades {
 	    $noupdate.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line;
 	    $noupdateCtr++;
 	}
+        if ($aggregateflag) {
+            &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
+                      $env{'course.'.$env{'request.course.id'}.'.domain'},
+                      $env{'course.'.$env{'request.course.id'}.'.num'});
+        }
     }
     if ($noupdate) {
 #	my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
@@ -3566,6 +3698,8 @@ sub updateGradeByPage {
 
 	    my %newrecord=();
 	    my @displayPts=();
+            my %aggregate = ();
+            my $aggregateflag = 0;
 	    foreach my $partid (@{$parts}) {
 		my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
 		my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
@@ -3592,6 +3726,14 @@ sub updateGradeByPage {
 		    $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
 		    $changeflag++;
 		    $newpts = '';
+                    
+                    my $aggtries =  $env{'form.aggtries'.$question.'_'.$partid};
+                    my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
+                    my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
+                    if ($aggtries > 0) {
+                        &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
+                        $aggregateflag = 1;
+                    }
 		}
 		my $display_part=&get_display_part($partid,undef,
 						   $curRes->symb());
@@ -3617,6 +3759,11 @@ sub updateGradeByPage {
 		&Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
 					$udom,$uname);
 	    }
+            if ($aggregateflag) {
+                &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
+                      $env{'course.'.$env{'request.course.id'}.'.domain'},
+                      $env{'course.'.$env{'request.course.id'}.'.num'});
+            }
 
 	    $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
 		'<td valign="top">'.$displayPts[1].'</td>'.
@@ -3718,6 +3865,7 @@ sub scantron_CODElist {
     my $namechoice='<option></option>';
     foreach my $name (sort {uc($a) cmp uc($b)} @names) {
 	if ($name =~ /^error: 2 /) { next; }
+	if ($name =~ /^type\0/) { next; }
 	$namechoice.='<option value="'.$name.'">'.$name.'</option>';
     }
     $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
@@ -3726,12 +3874,12 @@ sub scantron_CODElist {
 
 sub scantron_CODEunique {
     my $result='<nobr>
-                 <input type="radio" name="scantron_CODEunique"
-                        value="Yes" checked="on" /> Yes
+                 <label><input type="radio" name="scantron_CODEunique"
+                        value="Yes" checked="on" /> Yes </label>
                 </nobr>
                 <nobr>
-                 <input type="radio" name="scantron_CODEunique"
-                        value="No" /> No
+                 <label><input type="radio" name="scantron_CODEunique"
+                        value="No" /> No </label>
                 </nobr>';
     return $result;
 }
@@ -3781,8 +3929,8 @@ sub scantron_selectphase {
           <tr bgcolor="#ffffe6">
 	    <td> Options: </td>
             <td>
-                <input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records <br />
-                <input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all exisiting corrections
+	       <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>
 	    </td>
           </tr>
           <tr bgcolor="#ffffe6">
@@ -3961,7 +4109,14 @@ sub scantron_fixup_scanline {
 	    &scan_data($scan_data,
 		       "$whichline.no_bubble.".$args->{'question'},'1');
 	} else {
-	    substr($answer,$args->{'response'},1)=$on;
+	    if ($on eq 'letter') {
+		my @alphabet=('A'..'Z');
+		$answer=$alphabet[$args->{'response'}];
+	    } elsif ($on eq 'number') {
+		$answer=$args->{'response'}+1;
+	    } else {
+		substr($answer,$args->{'response'},1)=$on;
+	    }
 	    &scan_data($scan_data,
 		       "$whichline.no_bubble.".$args->{'question'},undef,'1');
 	}
@@ -3986,8 +4141,11 @@ sub scantron_parse_scanline {
     my %record;
     my $questions=substr($line,$$scantron_config{'Qstart'}-1);
     my $data=substr($line,0,$$scantron_config{'Qstart'}-1);
-    if ($$scantron_config{'CODElocation'} ne 0) {
-	if ($$scantron_config{'CODElocation'} < 0) {
+    if (!($$scantron_config{'CODElocation'} eq 0 ||
+	  $$scantron_config{'CODElocation'} eq 'none')) {
+	if ($$scantron_config{'CODElocation'} < 0 ||
+	    $$scantron_config{'CODElocation'} eq 'letter' ||
+	    $$scantron_config{'CODElocation'} eq 'number') {
 	    $record{'scantron.CODE'}=substr($data,
 					    $$scantron_config{'CODEstart'}-1,
 					    $$scantron_config{'CODElength'});
@@ -4022,8 +4180,12 @@ sub scantron_parse_scanline {
 	substr($questions,0,$$scantron_config{'Qlength'})='';
 	if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }
 	if ($$scantron_config{'Qon'} eq 'letter') {
-	    if (!$currentquest || $currentquest eq $$scantron_config{'Qoff'} ||
-		$currentquest !~ /^[A-Z]$/) {
+	    if ($currentquest eq '?') {
+		push(@{$record{'scantron.doubleerror'}},$questnum);
+		$record{"scantron.$questnum.answer"}='';
+	    } elsif (!$currentquest 
+		     || $currentquest eq $$scantron_config{'Qoff'}
+		     || $currentquest !~ /^[A-Z]$/) {
 		$record{"scantron.$questnum.answer"}='';
 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 		    push(@{$record{"scantron.missingerror"}},$questnum);
@@ -4032,8 +4194,12 @@ sub scantron_parse_scanline {
 		$record{"scantron.$questnum.answer"}=$currentquest;
 	    }
 	} elsif ($$scantron_config{'Qon'} eq 'number') {
-	    if (!$currentquest || $currentquest eq $$scantron_config{'Qoff'} ||
-		$currentquest !~ /^\d$/) {
+	    if ($currentquest eq '?') {
+		push(@{$record{'scantron.doubleerror'}},$questnum);
+		$record{"scantron.$questnum.answer"}='';
+		} elsif (!$currentquest 
+			 || $currentquest eq $$scantron_config{'Qoff'} 
+			 || $currentquest !~ /^\d$/) {
 		$record{"scantron.$questnum.answer"}='';
 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 		    push(@{$record{"scantron.missingerror"}},$questnum);
@@ -4418,9 +4584,8 @@ sub lonnet_putfile {
     my ($contents,$filename)=@_;
     my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
-    my $docuhome=$env{'course.'.$env{'request.course.id'}.'.home'};
     $env{'form.sillywaytopassafilearound'}=$contents;
-    &Apache::lonnet::finishuserfileupload($docuname,$docudom,$docuhome,'sillywaytopassafilearound',$filename);
+    &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
 
 }
 
@@ -4588,19 +4753,24 @@ sub scantron_get_correction {
 	$r->print("<p>How should I handle this? <br /> \n");
 	$r->print("\n<br /> ");
 	my $i=0;
-	if ($error eq 'incorrectCODE') {
+	if ($error eq 'incorrectCODE' 
+	    && $$scan_record{'scantron.CODE'}=~/\S/ ) {
 	    my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
-	    foreach my $testcode (@{$closest}) {
-		my $checked='';
-		if (!$i) { $checked=' checked="on" '; }
-		$r->print("<input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.<input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
-		$r->print("\n<br />");
-		$i++;
+	    if ($closest > 0) {
+		foreach my $testcode (@{$closest}) {
+		    my $checked='';
+		    if (!$i) { $checked=' checked="on" '; }
+		    $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.</label><input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
+		    $r->print("\n<br />");
+		    $i++;
+		}
 	    }
 	}
-	my $checked; if (!$i) { $checked=' checked="on" '; }
-	$r->print("<input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.");
-	$r->print("\n<br />");
+	if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
+	    my $checked; if (!$i) { $checked=' checked="on" '; }
+	    $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.</label>");
+	    $r->print("\n<br />");
+	}
 
 	$r->print(<<ENDSCRIPT);
 <script type="text/javascript">
@@ -4619,9 +4789,9 @@ ENDSCRIPT
 	   "&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='scantron_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='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />");
+	$r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_found' /> <a target='_blank' href='$href'>Select</a> a CODE from the list of all CODEs and use it.</label> Selected CODE is <input readonly='true' 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("<input type='radio' name='scantron_CODE_resolution' value='use_typed' /> Use <input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" /> as the CODE.");
+	$r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_typed' /> Use </label><input type='text' size='8' name='scantron_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') {
 	$r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n");
@@ -4654,21 +4824,26 @@ ENDSCRIPT
 sub scantron_bubble_selector {
     my ($r,$scan_config,$quest,@selected)=@_;
     my $max=$$scan_config{'Qlength'};
+
+    my $scmode=$$scan_config{'Qon'};
+    if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }	     
+
     my @alphabet=('A'..'Z');
     $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>");
     for (my $i=0;$i<$max+1;$i++) {
-	$r->print('<td align="center">');
+	$r->print("\n".'<td align="center">');
 	if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
 	else { $r->print('&nbsp;'); }
 	$r->print('</td>');
     }
-    $r->print('<td></td></tr><tr>');
+    $r->print('</tr><tr>');
     for (my $i=0;$i<$max;$i++) {
-	$r->print('<td><input type="radio" name="scantron_correct_Q_'.$quest.
-		  '" value="'.$i.'" />'.$alphabet[$i]."</td>");
+	$r->print("\n".
+		  '<td><label><input type="radio" name="scantron_correct_Q_'.
+		  $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
     }
-    $r->print('<td><input type="radio" name="scantron_correct_Q_'.$quest.
-	      '" value="none" /> No bubble </td>');
+    $r->print('<td><label><input type="radio" name="scantron_correct_Q_'.
+	      $quest.'" value="none" /> No bubble </label></td>');
     $r->print('</tr></table>');
 }
 
@@ -4697,8 +4872,14 @@ sub get_codes {
     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 %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"],
+				    $cdom,$cnum);
+    my %allcodes;
+    if ($result{"type\0$old_name"} eq 'number') {
+	%allcodes=map {($_,1)} split(',',$result{$old_name});
+    } else {
+	%allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
+    }
     return %allcodes;
 }
 
@@ -4988,8 +5169,6 @@ sub scantron_upload_scantron_data_save {
     }
     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
     $r->print("Doing upload to ".$coursedata{'description'}." <br />");
-    my $home=&Apache::lonnet::homeserver($env{'form.courseid'},
-					 $env{'form.domainid'});
     my $fname=$env{'form.upfile.filename'};
     #FIXME
     #copied from lonnet::userfileupload()
@@ -5009,7 +5188,7 @@ sub scantron_upload_scantron_data_save {
     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);
+	my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'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 {