Diff for /loncom/homework/grades.pm between versions 1.38 and 1.489

version 1.38, 2002/07/10 21:08:38 version 1.489, 2007/11/13 01:47:36
Line 25 Line 25
 #  #
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
 # 2/9,2/13 Guy Albertelli  
 # 6/8 Gerd Kortemeyer  
 # 7/26 H.K. Ng  
 # 8/20 Gerd Kortemeyer  
 # Year 2002  
 # June, July 2002 H.K. Ng  
 #  
   
 package Apache::grades;  package Apache::grades;
 use strict;  use strict;
Line 39  use Apache::style; Line 32  use Apache::style;
 use Apache::lonxml;  use Apache::lonxml;
 use Apache::lonnet;  use Apache::lonnet;
 use Apache::loncommon;  use Apache::loncommon;
   use Apache::lonhtmlcommon;
   use Apache::lonnavmaps;
 use Apache::lonhomework;  use Apache::lonhomework;
 use Apache::lonmsg qw(:user_normal_msg);  use Apache::lonpickcode;
   use Apache::loncoursedata;
   use Apache::lonmsg();
 use Apache::Constants qw(:common);  use Apache::Constants qw(:common);
 use Time::HiRes qw( gettimeofday tv_interval );  use Apache::lonlocal;
   use Apache::lonenc;
   use String::Similarity;
   use LONCAPA;
   
 sub moreinfo {  use POSIX qw(floor);
   my ($request,$reason) = @_;  
   $request->print("Unable to process request: $reason");  
   if ( $Apache::grades::viewgrades eq 'F' ) {  
     $request->print('<form action="/adm/grades" method="post">'."\n");  
     if ($ENV{'form.url'}) {  
       $request->print('<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />'."\n");  
     }  
     if ($ENV{'form.symb'}) {  
       $request->print('<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />'."\n");  
     }  
 #    $request->print('<input type="hidden" name="command" value="submission" />'."\n");  
     $request->print('<input type="hidden" name="command" value="'.$ENV{'form.command'}.'" />'."\n");  
     $request->print("Student:".'<input type="text" name="student" value="'.$ENV{'form.student'}.'" />'."<br />\n");  
     $request->print("Domain:".'<input type="text" name="domain" value="'.$ENV{'user.domain'}.'" />'."<br />\n");  
     $request->print('<input type="submit" name="submit" value="ReSubmit" />'."<br />\n");  
     $request->print('</form>');  
   }  
   return '';  
 }  
   
 sub verifyreceipt {  
     my $request=shift;  my %perm=();
     my $courseid=$ENV{'request.course.id'};  my %bubble_lines_per_response = ();     # no. bubble lines for each response.
 #    my $cdom=$ENV{"course.$courseid.domain"};                                     # index is "symb.part_id"
 #    my $cnum=$ENV{"course.$courseid.num"};  
     my $receipt=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'.  my %first_bubble_line = (); # First bubble line no. for each bubble.
                 $ENV{'form.receipt'};  
     $receipt=~s/[^\-\d]//g;  # Save and restore the bubble lines array to the form env.
     my $symb=$ENV{'form.symb'};  
     unless ($symb) {  
  $symb=&Apache::lonnet::symbread($ENV{'form.url'});  sub save_bubble_lines {
     }      foreach my $line (keys(%bubble_lines_per_response)) {
     if ((&Apache::lonnet::allowed('mgr',$courseid)) && ($symb)) {   $env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
         $request->print('<h1>Verifying Submission Receipt '.$receipt.'</h1>');   $env{"form.scantron.first_bubble_line.$line"} =
         my $matches=0;      $first_bubble_line{$line};
         my ($classlist) = &getclasslist('all','0');  
         foreach my $student ( sort(@{ $$classlist{'all'} }) ) {  
             my ($uname,$udom)=split(/\:/,$student);  
             if ($receipt eq   
              &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) {  
                $request->print('Matching '.$student.'<br>');  
                $matches++;  
    }  
         }  
         $request->printf('<p>'.$matches." match%s</p>",$matches <= 1 ? '' : 'es');  
 # needs to print who is matched  
     }      }
     return '';  
 }  }
   
 sub student_gradeStatus {  
   my ($url,$udom,$uname) = @_;  sub restore_bubble_lines {
   my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));      my $line = 0;
   my %record= &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);      %bubble_lines_per_response = ();
   foreach my $part (&getpartlist($url)) {      while ($env{"form.scantron.bubblelines.$line"}) {
     my ($temp,$part,$type)=split(/_/,$part);   my $value = $env{"form.scantron.bubblelines.$line"};
     if ($type eq 'solved') {   $bubble_lines_per_response{$line} = $value;
       my ($status,$foo)=split(/_/,$record{"resource.$part.$type"},2);   $first_bubble_line{$line}  =
       $status = 'partial' if ($foo =~ /^partially/);      $env{"form.scantron.first_bubble_line.$line"};
       $status = 'nothing' if ($status eq '');   $line++;
       return $type,$status;  
     }      }
   }  
   return '';  
 }  }
   
 sub get_fullname {  #  Given the parsed scanline, get the response for 
     my ($sname,$sdom) = @_;  #  'answer' number n:
     my %name=&Apache::lonnet::get('environment', ['lastname','generation',  
   'firstname','middlename'],  sub get_response_bubbles {
   $sdom,$sname);      my ($parsed_line, $response)  = @_;
     my $fullname;  
     my ($tmp) = keys(%name);  
     if ($tmp !~ /^(con_lost|error|no_such_host)/i) {      my $bubble_line = $first_bubble_line{$response-1} +1;
  $fullname=$name{'lastname'}.$name{'generation'};      my $bubble_lines= $bubble_lines_per_response{$response-1};
  if ($fullname =~ /[^\s]+/) { $fullname.=', '; }      
  $fullname.=$name{'firstname'}.' '.$name{'middlename'};      my $selected = "";
   
       for (my $bline = 0; $bline < $bubble_lines; $bline++) {
    $selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":";
    $bubble_line++;
     }      }
     return $fullname;      return $selected;
 }  }
   
 sub listStudents {  
   my ($request) = shift;  
   my $cdom      =$ENV{"course.$ENV{'request.course.id'}.domain"};  
   my $cnum      =$ENV{"course.$ENV{'request.course.id'}.num"};  
   my $getsec    =$ENV{'form.section'};  
   my $submitonly=$ENV{'form.submitonly'};  
   
   $request->print(<<ENDTABLEST);  
 <h2><font color="#339933">&nbsp;View Submissions for a Student or a Group of Students</font></h2>  
 &nbsp;<font size=+1><b>Resource:</b> $ENV{'form.url'}<br /><br />  
 <form action="/adm/grades" method="post">&nbsp;<b>View Options</b></font><br />  
 &nbsp;<b>View Problem: </b><input type="radio" name="vProb" value="no" checked> no   
 <input type="radio" name="vProb" value="yes"> yes <br />  
 &nbsp;<b>Submissions: </b>  
 <input type="radio" name="lastSub" value="lastonly" checked> last sub only  
 <input type="radio" name="lastSub" value="last"> last sub & parts info  
 <input type="radio" name="lastSub" value="all"> all details  
 <input type="hidden" name="section" value="$getsec">  
 <input type="hidden" name="submitonly" value="$submitonly">  
 <input type="hidden" name="response" value="$ENV{'form.response'}">  
 <input type="hidden" name="handgrade" value="$ENV{'form.handgrade'}">  
 <table border="0"><tr><td bgcolor="#777777">  
 <table border="0"><tr bgcolor="#e6ffff">  
 <td><b>&nbsp;Select&nbsp;</b></td><td><b>&nbsp;Username&nbsp;</b></td>  
 <td><b>&nbsp;Fullname&nbsp;</b></td><td><b>&nbsp;Domain&nbsp;</b></td>  
 <td><b>&nbsp;Grade Status&nbsp;</b></td></tr>  
 ENDTABLEST  
   if ($ENV{'form.url'}) {  
       $request->print('<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />'."\n");  
   }  
   if ($ENV{'form.symb'}) {  
       $request->print('<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />'."\n");  
   }  
   $request->print('<input type="hidden" name="command" value="processGroup" />'."\n");  
   
   my $t0=&Time::HiRes::time();  # ----- These first few routines are general use routines.----
   
   my ($classlist) = &getclasslist($getsec,'0');  # Return the number of occurences of a pattern in a string.
   my $t1=&Time::HiRes::time();  
   print "getclasslist=",$t1-$t0,"<br />";  
   
   foreach my $student ( sort(@{ $$classlist{$getsec} }) ) {  
       my ($sname,$sdom) = split(/:/,$student);  
       my ($type,$status) = &student_gradeStatus($ENV{'form.url'},$cdom,$sname);  
       next if ($status eq 'nothing' && $submitonly eq 'yes');  
   
       my $fullname = &get_fullname($sname,$sdom);  
       if ( $Apache::grades::viewgrades eq 'F' ) {  
   $request->print("\n".'<tr bgcolor="#ffffe6">'.  
   '<td align="center"><input type=checkbox name="stuinfo" value="'.  
   $student.':'.$fullname.'"></td>'."\n".  
   '<td>&nbsp;'.$sname.'&nbsp;</td>'."\n".  
   '<td>&nbsp;'.$fullname.'&nbsp;</td>'."\n".  
   '<td align="middle">&nbsp;'.$sdom.'&nbsp;</td>'."\n");  
   $request->print('<td align="middle">&nbsp;'.$status.'&nbsp;</td>'."\n");  
   
   $request->print('</tr>');  sub occurence_count {
       }      my ($string, $pattern) = @_;
   }  
   my $t2=&Time::HiRes::time();  
   print "processclasslist=",$t2-$t1,"<br />";  
   $request->print('</table></td></tr></table>');  
   $request->print('<input type="submit" name="submit" value="View/Grade" /><form />');  
 }  
   
 sub processGroup {      my @matches = ($string =~ /$pattern/g);
   my ($request)  = shift;  
   my $ctr        = 0;      return scalar(@matches);
   my @stuchecked = (ref($ENV{'form.stuinfo'}) ? @{$ENV{'form.stuinfo'}}  
                            : ($ENV{'form.stuinfo'}));  
   my $total      = scalar(@stuchecked)-1;  
   if ($stuchecked[0] eq '') {  
       &userError($request,'No student was selected for viewing/grading.');  
       return;  
   }  
   foreach (@stuchecked) {  
       my ($sname,$sdom,$fullname) = split(/:/);  
       $ENV{'form.student'} = $sname;  
       $ENV{'form.fullname'} = $fullname;  
       &submission($request,$ctr,$total);  
       $ctr++;  
   }  
   return 'The End';  
 }  }
   
 sub userError {  
     my ($request, $reason, $step) = @_;  # Take a string known to have digits and convert all the
     $request->print('<h3><font color="red">LON-CAPA User Error</font></h3><br />'."\n");  # digits into letters in the range J,A..I.
     $request->print('<b>Reason: </b>'.$reason.'<br /><br />'."\n");  
     $request->print('<b>Step: </b>'.($step ne '' ? $step : 'Use your browser back button to correct')  sub digits_to_letters {
     .'<br /><br />'."\n");      my ($input) = @_;
     return '';  
       my @alphabet = ('J', 'A'..'I');
   
       my @input    = split(//, $input);
       my $output ='';
       for (my $i = 0; $i < scalar(@input); $i++) {
    if ($input[$i] =~ /\d/) {
       $output .= $alphabet[$input[$i]];
    } else {
       $output .= $input[$i];
    }
       }
       return $output;
 }  }
   
 #FIXME - needs to handle multiple matches  #
 sub finduser {  # --- Retrieve the parts from the metadata file.---
   my ($name) = @_;  sub getpartlist {
   my $domain = '';      my ($symb) = @_;
   if ( $Apache::grades::viewgrades eq 'F' ) {  
     my ($classlist) = &getclasslist('all','0');      my $navmap   = Apache::lonnavmaps::navmap->new();
     foreach ( sort(@{ $$classlist{'all'} }) ) {      my $res      = $navmap->getBySymb($symb);
       my ($posname,$posdomain) = split(/:/);      my $partlist = $res->parts();
       if ($posname =~ $name) { $name=$posname; $domain=$posdomain; last; }      my $url      = $res->src();
     }      my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
     return ($name,$domain);  
   } else {      my @stores;
     return ($ENV{'user.name'},$ENV{'user.domain'});      foreach my $part (@{ $partlist }) {
   }   foreach my $key (@metakeys) {
       if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
    }
       }
       return @stores;
 }  }
   
 sub getclasslist {  # --- Get the symbolic name of a problem and the url
   my ($getsec,$hideexpired) = @_;  sub get_symb {
   my ($coursedomain,$coursenum) = split(/_/,$ENV{'request.course.id'});      my ($request,$silent) = @_;
   my %classlist=&Apache::lonnet::dump('classlist',$coursedomain,$coursenum);      (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
   my $now = time;      my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
   my (@holdsec,@sections);      if ($symb eq '') { 
   foreach my $student (keys(%classlist)) {   if (!$silent) {
     my ($end,$start)=split(/:/,$classlist{$student});      $request->print("Unable to handle ambiguous references:$url:.");
     # still a student?      return ();
     if (($hideexpired) && ($end) && ($end < $now)) {   }
       next;      }
     }      &Apache::lonenc::check_decrypt(\$symb);
     my ($unam,$udom) = split(/:/,$student,2);      return ($symb);
     my $section = &Apache::lonnet::usection($udom,$unam,$ENV{'request.course.id'});  
     $section = ($section ne '-1' ? $section : 'no');  
     push @holdsec,$section;  
     push (@{ $classlist{$getsec} }, $student) if ($getsec eq 'all' || $getsec eq $section);  
   }  
   my %seen = ();  
   foreach my $item (@holdsec) {  
       push (@sections, $item) unless $seen{$item}++;  
   }  
   return (\%classlist,\@sections);  
 }  }
   
 sub getpartlist {  #--- Format fullname, username:domain if different for display
   my ($url) = @_;  #--- Use anywhere where the student names are listed
   my @parts =();  sub nameUserString {
   my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));      my ($type,$fullname,$uname,$udom) = @_;
   foreach my $key (@metakeys) {      if ($type eq 'header') {
     if ( $key =~ m/stores_([0-9]+)_.*/) {   return '<b>&nbsp;'.&mt('Fullname').'&nbsp;</b><span class="LC_internal_info">('.&mt('Username').')</span>';
       push(@parts,$key);      } else {
    return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
       ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
     }      }
   }  
   return @parts;  
 }  }
   
 sub viewstudentgrade {  #--- Get the partlist and the response type for a given problem. ---
   my ($url,$symb,$courseid,$student,@parts) = @_;  #--- Indicate if a response type is coded handgraded or not. ---
   my $result ='';  sub response_type {
   my $cellclr = '"#ffffdd"';      my ($symb) = shift;
   my ($username,$domain) = split(/:/,$student);  
       my $navmap = Apache::lonnavmaps::navmap->new();
   my $fullname = &get_fullname($username,$domain);      my $res = $navmap->getBySymb($symb);
   my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$username);      my $partlist = $res->parts();
       my %vPart = 
   $result.="<tr bgcolor=$cellclr><td>$username</td><td>$fullname</td><td align=\"middle\">$domain</td>\n";   map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
   foreach my $part (@parts) {      my (%response_types,%handgrade);
     my ($temp,$part,$type)=split(/_/,$part);      foreach my $part (@{ $partlist }) {
     my $score=$record{"resource.$part.$type"};   next if (%vPart && !exists($vPart{$part}));
     if ($type eq 'awarded' || $type eq 'tries') {  
       $result.='<td align="middle"><input type="text" name="GRADE.'.$student.'.'.$part.'.'.$type.   my @types = $res->responseType($part);
   '" value="'.$score.'" size="4" /></td>'."\n";   my @ids = $res->responseIds($part);
     } elsif ($type eq 'solved') {   for (my $i=0; $i < scalar(@ids); $i++) {
       my ($status,$foo)=split(/_/,$score,2);      $response_types{$part}{$ids[$i]} = $types[$i];
       $result.="<td align=\"middle\"><select name=\"GRADE.$student.$part.$type\">\n";      $handgrade{$part.'_'.$ids[$i]} = 
       my $optsel = '<option>correct</option><option>incorrect</option><option>excused</option>'.   &Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
   '<option>ungraded</option><option>partial</option><option>nothing</option>'."\n";       '.handgrade',$symb);
       $status = 'nothing' if ($status eq '');   }
       $optsel =~ s/<option>$status/<option selected="on">$status/;  
       $result.=$optsel;  
       $result.="</select></td>\n";  
     }      }
   }      return ($partlist,\%handgrade,\%response_types);
   $result.='</td></tr>';  
   return $result;  
 }  }
   
 #FIXME need to look at the metadata <stores> spec on what type of data to accept and provide an  sub flatten_responseType {
 #interface based on that, also do that to above function.      my ($responseType) = @_;
 sub setstudentgrade {      my @part_response_id =
   my ($url,$symb,$courseid,$student,@parts) = @_;   map { 
   print "set student grade parts=@parts<br>";      my $part = $_;
   my $result ='';      map {
   my ($stuname,$domain) = split(/:/,$student);   [$part,$_]
   my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$stuname);   } sort(keys(%{ $responseType->{$part} }));
   my %newrecord;   } sort(keys(%$responseType));
       return @part_response_id;
   foreach my $part (@parts) {  }
     my ($temp,$part,$type)=split(/_/,$part);  
     my $oldscore=$record{"resource.$part.$type"};  sub get_display_part {
     my $newscore=$ENV{"form.GRADE.$student.$part.$type"};      my ($partID,$symb)=@_;
     print "old=$oldscore:new=$newscore:<br>";      my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
     if ($type eq 'solved') {      if (defined($display) and $display ne '') {
       my $update=0;   $display.= " (<span class=\"LC_internal_info\">id $partID</span>)";
       if ($newscore eq 'nothing' ) {  
  if ($oldscore ne '') {  
   $update=1;  
   $newscore = '';  
  }  
       } elsif ($oldscore !~ m/^$newscore/) {  
  $update=1;  
  $result.="Updating $stuname to $newscore<br />\n";  
  if ($newscore eq 'correct')   { $newscore = 'correct_by_override'; }  
  if ($newscore eq 'incorrect') { $newscore = 'incorrect_by_override'; }  
  if ($newscore eq 'excused')   { $newscore = 'excused'; }  
  if ($newscore eq 'ungraded')  { $newscore = 'ungraded_attempted'; }  
  if ($newscore eq 'partial')   { $newscore = 'correct_partially_by_override'; }  
       } else {  
  #$result.="$stuname:$part:$type:unchanged  $oldscore to $newscore:<br />\n";  
       }  
       if ($update) { $newrecord{"resource.$part.$type"}=$newscore; }  
     } else {      } else {
       if ($oldscore ne $newscore) {   $display=$partID;
  $newrecord{"resource.$part.$type"}=$newscore;  
  $result.="Updating $student"."'s status for $part.$type to $newscore<br />\n";  
       } else {  
  #$result.="$stuname:$part:$type:unchanged  $oldscore to $newscore:<br />\n";  
       }  
     }      }
   }      return $display;
   if ( scalar(keys(%newrecord)) > 0 ) {  }
     $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";  
 #    &Apache::lonnet::cstore(\%newrecord,$symb,$courseid,$domain,$stuname);  
   
     $result.="Stored away ".scalar(keys(%newrecord))." elements.<br />\n";  #--- Show resource title
   }  #--- and parts and response type
   return $result;  sub showResourceInfo {
       my ($symb,$probTitle,$checkboxes) = @_;
       my $col=3;
       if ($checkboxes) { $col=4; }
       my $result = '<h3>'.&mt('Current Resource').': '.$probTitle.'</h3>'."\n";
       $result .='<table border="0">';
       my ($partlist,$handgrade,$responseType) = &response_type($symb);
       my %resptype = ();
       my $hdgrade='no';
       my %partsseen;
       foreach my $partID (sort keys(%$responseType)) {
    foreach my $resID (sort keys(%{ $responseType->{$partID} })) {
       my $handgrade=$$handgrade{$partID.'_'.$resID};
       my $responsetype = $responseType->{$partID}->{$resID};
       $hdgrade = $handgrade if ($handgrade eq 'yes');
       $result.='<tr>';
       if ($checkboxes) {
    if (exists($partsseen{$partID})) {
       $result.="<td>&nbsp;</td>";
    } else {
       $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='checked' /></td>";
    }
    $partsseen{$partID}=1;
       }
       my $display_part=&get_display_part($partID,$symb);
       $result.='<td>'.&mt('<b>Part: </b>[_1]',$display_part).' <span class="LC_internal_info">'.
    $resID.'</span></td>'.
    '<td>'.&mt('<b>Type: </b>[_1]',$responsetype).'</td></tr>';
   #    '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>';
    }
       }
       $result.='</table>'."\n";
       return $result,$responseType,$hdgrade,$partlist,$handgrade;
 }  }
   
 #  sub reset_caches {
 # --------------------------- show submissions of a student, option to grade --------      &reset_analyze_cache();
 sub submission {      &reset_perm();
   my ($request,$counter,$total) = @_;  }
   
   (my $url=$ENV{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;  {
   if ($ENV{'form.student'} eq '') { &moreinfo($request,'Need student login id'); return ''; }      my %analyze_cache;
   my ($uname,$udom) = &finduser($ENV{'form.student'});  
   if ($uname eq '') { &moreinfo($request,'Unable to find student'); return ''; }  
   
   my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));  
   if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }  
   my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : '');  
   
   # header info  
   if ($counter == 0) {  
       &sub_page_js($request);  
       $request->print('<h2>&nbsp;<font color="#339933">Submission Record</font></h2>'.  
       '<font size=+1>&nbsp;<b>Resource: </b>'.$url.'</font>');  
   
       # option to display problem, only once else it cause problems with the form later   
       # since the problem has a form.  
       if ($ENV{'form.vProb'} eq 'yes') {  
   my $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,  
     $ENV{'request.course.id'});  
   my $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom,  
  $ENV{'request.course.id'});  
   my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';  
   $result.='<table border="0" width="100%"><tr><td bgcolor="#e6ffff">';  
   $result.='<b>Student\'s view of the problem</b></td></tr><tr><td bgcolor="#ffffff">'.$rendered.'<br />';  
   $result.='<b>Correct answer:</b><br />'.$companswer;  
   $result.='</td></tr></table>';  
   $result.='</td></tr></table><br />';  
   $request->print($result);  
       }  
   
       # kwclr is the only variable that is non blank if this has been used once.      sub reset_analyze_cache {
       my %keyhash = ();   undef(%analyze_cache);
       if ($ENV{'form.kwclr'} eq '') {      }
   %keyhash = &Apache::lonnet::dump('nohist_handgrade',  
       $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},  
       $ENV{'course.'.$ENV{'request.course.id'}.'.num'});  
   
   my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};  
   $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';  
   $ENV{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';  
   $ENV{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';  
   $ENV{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';  
   $ENV{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ?   
       $keyhash{$symb.'_subject'} : &Apache::lonnet::metadata($url,'title');  
   $ENV{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';  
   
       }      sub get_analyze {
       $request->print('<form action="/adm/grades" method="post" name="SCORE">'."\n".   my ($symb,$uname,$udom)=@_;
       '<input type="hidden" name="command"    value="handgrade" />'."\n".   my $key = "$symb\0$uname\0$udom";
       '<input type="hidden" name="symb"       value="'.$symb.'" />'."\n".   return $analyze_cache{$key} if (exists($analyze_cache{$key}));
       '<input type="hidden" name="url"        value="'.$url.'" />'."\n".  
       '<input type="hidden" name="vProb"      value="'.$ENV{'form.vProb'}.'" />'."\n".   my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
       '<input type="hidden" name="lastSub"    value="'.$ENV{'form.lastSub'}.'" />'."\n".   $url=&Apache::lonnet::clutter($url);
       '<input type="hidden" name="section"    value="'.$ENV{'form.section'}.'">'."\n".   my $subresult=&Apache::lonnet::ssi($url,
       '<input type="hidden" name="submitonly" value="'.$ENV{'form.submitonly'}.'">'."\n".     ('grade_target' => 'analyze'),
       '<input type="hidden" name="response"   value="'.$ENV{'form.response'}.'">'."\n".     ('grade_domain' => $udom),
       '<input type="hidden" name="handgrade"  value="'.$ENV{'form.handgrade'}.'">'."\n".     ('grade_symb' => $symb),
       '<input type="hidden" name="keywords"   value="'.$ENV{'form.keywords'}.'" />'."\n".     ('grade_courseid' => 
       '<input type="hidden" name="kwclr"      value="'.$ENV{'form.kwclr'}.'" />'."\n".      $env{'request.course.id'}),
       '<input type="hidden" name="kwsize"     value="'.$ENV{'form.kwsize'}.'" />'."\n".     ('grade_username' => $uname));
       '<input type="hidden" name="kwstyle"    value="'.$ENV{'form.kwstyle'}.'" />'."\n".   (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
       '<input type="hidden" name="msgsub"     value="'.$ENV{'form.msgsub'}.'" />'."\n".   my %analyze=&Apache::lonnet::str2hash($subresult);
       '<input type="hidden" name="savemsgN"   value="'.$ENV{'form.savemsgN'}.'" />'."\n".   return $analyze_cache{$key} = \%analyze;
       '<input type="hidden" name="NCT"'.      }
       ' value="'.($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : $total+1).'" />'."\n");  
   
       my ($cts,$prnmsg) = (1,'');  
       while ($cts <= $ENV{'form.savemsgN'}) {  
   $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.  
       ($keyhash{$symb.'_savemsg'.$cts} eq '' ? $ENV{'form.savemsg'.$cts} : $keyhash{$symb.'_savemsg'.$cts}).  
       '" />'."\n";  
   $cts++;  
       }  
       $request->print($prnmsg);  
   
       if ($ENV{'form.handgrade'} eq 'yes') {      sub get_order {
   $request->print(<<KEYWORDS);   my ($partid,$respid,$symb,$uname,$udom)=@_;
 &nbsp;<b>Keyword Options:</b>&nbsp;   my $analyze = &get_analyze($symb,$uname,$udom);
 <a href="javascript:keywords(document.SCORE.keywords)"; TARGET=_self>List</a>&nbsp; &nbsp;   return $analyze->{"$partid.$respid.shown"};
 <a href="#" onMouseDown="javascript:getSel(); return false"      }
  CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;  
 <a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br />  
 KEYWORDS  
       }  
   }  
   
   # Student info      sub get_radiobutton_correct_foil {
   $request->print(($counter == 0 ? '' : '<br />'));   my ($partid,$respid,$symb,$uname,$udom)=@_;
   my $fullname = ($ENV{'form.fullname'} ne '' ? $ENV{'form.fullname'} : &get_fullname($uname,$udom));   my $analyze = &get_analyze($symb,$uname,$udom);
   my $result.='<table border="0" width=100%><tr><td bgcolor="#777777">'.   foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) {
       '<table border="0" width=100%><tr bgcolor="#ffffff"><td>';      if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
   $result.='<table border="0"><tr bgcolor="#ffffff"><td><b>Username: </b>'.$uname.   return $foil;
       '</td><td><b>Fullname: </b>'.$fullname.      }
       '</td><td><b>Domain: </b>'.$udom.'</td></tr>';   }
   if ($ENV{'form.handgrade'} eq 'yes') {      }
 #      my $subonly = &get_last_submission($symb,$uname,$udom,$ENV{'request.course.id'});  }
       my ($classlist) = &getclasslist('all','0');  
       my @collaborators;  
 #      foreach ( sort(@{ $$classlist{'all'} }) ) {  
 #  my ($sname,$sdom) = split(/:/);  
 #  push @collaborators,$sname if (grep /\b$sname(\b|\.)/i,$subonly);  
 #      }  
 #      push @collaborators,'leede','carlandmm','freyniks'; # as a test to display collaborators.  
       if (scalar(@collaborators) != 0) {  
   $result.='<tr bgcolor="#ffffff"><td colspan=3><b>Collaborators: </b>';  
   foreach (@collaborators) {  
       $result.=$_.' ('.&get_fullname($_,$udom).') &nbsp; &nbsp;';  
   }  
   $result.='</td></tr>'."\n";  
   $result.='<input type="hidden" name="collaborator'.$counter.  
       '" value="'.(join ':',@collaborators).'" />'."\n";  
       }  
   }  
   $result.='</table>'."\n";  
   $request->print($result);  
   
  # print student answer  #--- Clean response type for display
   if ($ENV{'form.lastSub'} eq 'lastonly') {  #--- Currently filters option/rank/radiobutton/match/essay/Task
       my ($string,$timestamp)=&get_last_submission ($symb,$uname,$udom,$ENV{'request.course.id'});  #        response types only.
       $string=&keywords_highlight(join('::::',@$string));  sub cleanRecord {
       my $lastsubonly='<table border="0" width=100%><tr><td bgcolor="#777777">';      my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
       $lastsubonly.='<table border="0" width=100%><tr bgcolor="#ddffff">';   $uname,$udom) = @_;
       $lastsubonly.='<td><b>Last Submission Only</b>'.      my $grayFont = '<span class="LC_internal_info">';
   ($timestamp eq '' ? '' : '&nbsp; &nbsp; <b>Date Submitted:</b> '.@$timestamp).'</td></tr>';      if ($response =~ /^(option|rank)$/) {
       $lastsubonly.='<tr><td bgcolor="#ffffe6">';   my %answer=&Apache::lonnet::str2hash($answer);
       $lastsubonly.=$string;   my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
       $lastsubonly.='</td></tr></table></td></tr></table>'."\n";   my ($toprow,$bottomrow);
       $request->print($lastsubonly);   foreach my $foil (@$order) {
   } else {      if ($grading{$foil} == 1) {
       $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,   $toprow.='<td><b>'.$answer{$foil}.'&nbsp;</b></td>';
        $ENV{'request.course.id'},$last,      } else {
        '.submission','Apache::grades::keywords_highlight'));   $toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
   }      }
       $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
       $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
       } elsif ($response eq 'match') {
    my %answer=&Apache::lonnet::str2hash($answer);
    my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
    my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
    my ($toprow,$middlerow,$bottomrow);
    foreach my $foil (@$order) {
       my $item=shift(@items);
       if ($grading{$foil} == 1) {
    $toprow.='<td><b>'.$item.'&nbsp;</b></td>';
    $middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</span></b></td>';
       } else {
    $toprow.='<td><i>'.$item.'&nbsp;</i></td>';
    $middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</span></i></td>';
       }
       $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
       $middlerow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
       $bottomrow.'</tr>'.'</table></blockquote>';
       } elsif ($response eq 'radiobutton') {
    my %answer=&Apache::lonnet::str2hash($answer);
    my ($toprow,$bottomrow);
    my $correct = 
       &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);
    foreach my $foil (@$order) {
       if (exists($answer{$foil})) {
    if ($foil eq $correct) {
       $toprow.='<td><b>'.&mt('true').'</b></td>';
    } else {
       $toprow.='<td><i>'.&mt('true').'</i></td>';
    }
       } else {
    $toprow.='<td>'.&mt('false').'</td>';
       }
       $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
       $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
       } elsif ($response eq 'essay') {
    if (! exists ($env{'form.'.$symb})) {
       my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
     $env{'course.'.$env{'request.course.id'}.'.domain'},
     $env{'course.'.$env{'request.course.id'}.'.num'});
   
       my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
       $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
       $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
       $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
       $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
       $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
    }
    $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;
       } elsif ( $response eq 'Task') {
    if ( $answer eq 'SUBMITTED') {
       my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"};
       my $result = &Apache::bridgetask::file_list($files,$uname,$udom);
       return $result;
    } elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) {
       my @matches = grep(/^\Q$version\E.*?\.instance$/,
          keys(%{$record}));
       return join('<br />',($version,@matches));
          
          
    } else {
       my $result =
    '<p>'
    .&mt('Overall result: [_1]',
        $record->{$version."resource.$respid.$partid.status"})
    .'</p>';
       
       $result .= '<ul>';
       my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
        keys(%{$record}));
       foreach my $grade (sort(@grade)) {
    my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
    $result.= '<li>'.&mt("Dimension: [_1], status [_2] ",
        $dim, $record->{$grade}).
     '</li>';
       }
       $result.='</ul>';
       return $result;
    }
       } elsif ( $response =~ m/(?:numerical|formula)/) {
    $answer = 
       &Apache::loncommon::format_previous_attempt_value('submission',
         $answer);
       }
       return $answer;
   }
   
   my $wgt    = &Apache::lonnet::EXT('resource.partid.weight',$symb,$udom,$uname);  #-- A couple of common js functions
   my $wgtmsg = ($wgt > 0 ? '(problem weight)' : '<font color="red">problem weight assigned by computer</font>');  sub commonJSfunctions {
   $wgt       = ($wgt > 0 ? $wgt : '1');      my $request = shift;
   my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);      $request->print(<<COMMONJSFUNCTIONS);
   my $score  = ($record{'resource.0.awarded'} eq '' ? '' : $record{'resource.0.awarded'}*$wgt);  <script type="text/javascript" language="javascript">
       function radioSelection(radioButton) {
   # display grading options   var selection=null;
   $result='<input type="hidden" name="WGT'.$counter.'" value="'.$wgt.'" />'.   if (radioButton.length > 1) {
       '<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'.$udom.'" />'."\n";      for (var i=0; i<radioButton.length; i++) {
   $result.='<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";   if (radioButton[i].checked) {
   $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n";      return radioButton[i].value;
   $result.='<table border="0"><tr><td><b>Points</b></td><td>';   }
       }
   my $ctr = 0;   } else {
   $result.='<table border="0"><tr>';  # display radio buttons in a nice table with 10 across      if (radioButton.checked) return radioButton.value;
   while ($ctr<=$wgt) {   }
       $result.= '<td><input type="radio" name="RADVAL'.$counter.'" '.   return selection;
   'onclick="javascript:writeBox(this.form.GRADE_BOX'.$counter.      }
     ',this.form.GRADE_SEL'.$counter.','.$ctr.','.$wgt.')" '.  
  ($score eq $ctr ? 'checked':'').' /> '.$ctr."</td>\n";  
       $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');  
       $ctr++;  
   }  
   $result.='</tr></table>';  
   
   $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>';      function pullDownSelection(selectOne) {
   $result.='<td><input type="text" name="GRADE_BOX'.$counter.'"'.   var selection="";
       ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.   if (selectOne.length > 1) {
        'onChange="javascript:updateRadio(this.form.RADVAL'.$counter.      for (var i=0; i<selectOne.length; i++) {
         ',this.form.GRADE_BOX'.$counter.   if (selectOne[i].selected) {
    ',this.form.GRADE_SEL'.$counter.',\''.$wgt.'\')" /></td>'."\n";      return selectOne[i].value;
   $result.='<td>/'.$wgt.' '.$wgtmsg.' </td><td>';   }
       }
   foreach my $part (&getpartlist($url)) {   } else {
       print "parts=$part<br>";              // only one value it must be the selected one
     my ($temp,$part,$type)=split(/_/,$part);      return selectOne.value;
     if ($type eq 'solved') {   }
       my ($status,$foo)=split(/_/,$record{"resource.$part.$type"},2);  
       $status = 'partial' if ($foo =~ /partially/);  
       $status = 'nothing' if ($status eq '');  
       $result.='<select name="GRADE_SEL'.$counter.'">'."\n";  
       my $optsel = '<option>correct</option><option>incorrect</option>'.  
   '<option>excused</option><option>ungraded</option>'.  
       '<option>partial</option><option>nothing</option>'."\n";  
       $optsel =~ s/<option>$status/<option selected="on">$status/;  
       $result.=$optsel;  
       $result.="</select>&nbsp&nbsp\n";  
     }      }
   }  </script>
   $result.='<a href="javascript:msgCenter(document.SCORE,'.$counter.  COMMONJSFUNCTIONS
       ',\''.$fullname.'\')"; TARGET=_self>Compose Message</a></td></tr></table>'."\n";  }
 #  $result.='</table>';  
   $result.='</td></tr></table></td></tr></table>';  
   $request->print($result);  
   
   # print end of form  
   if ($counter == $total) {  
       my $endform.='<table border="0"><tr><td><input type="submit" name="gradeOpt" value="Save & Next" />';  
       my $ntstu ='<select name="NTSTU">'.  
   '<option>1</option><option>2</option>'.  
       '<option>3</option><option>5</option>'.  
   '<option>7</option><option>10</option></select>'."\n";  
       my $nsel = ($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1');  
       $ntstu =~ s/<option>$nsel/<option selected="on">$nsel/;  
       $endform.=$ntstu.'student(s) &nbsp;&nbsp;';  
       $endform.='<input type="submit" name="gradeOpt" value="Next" />&nbsp';  
       $endform.='<input type="submit" name="gradeOpt" value="Previous" />&nbsp';  
       $endform.='(Next and Previous do not save the scores.)';  
       $endform.='</td><tr></table></form>';  
       $request->print($endform);  
   }  
   
   return '';  #--- Dumps the class list with usernames,list of sections,
   #--- section, ids and fullnames for each user.
   sub getclasslist {
       my ($getsec,$filterlist,$getgroup) = @_;
       my @getsec;
       my @getgroup;
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
       if (!ref($getsec)) {
    if ($getsec ne '' && $getsec ne 'all') {
       @getsec=($getsec);
    }
       } else {
    @getsec=@{$getsec};
       }
       if (grep(/^all$/,@getsec)) { undef(@getsec); }
       if (!ref($getgroup)) {
    if ($getgroup ne '' && $getgroup ne 'all') {
       @getgroup=($getgroup);
    }
       } else {
    @getgroup=@{$getgroup};
       }
       if (grep(/^all$/,@getgroup)) { undef(@getgroup); }
   
       my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist();
       # Bail out if we were unable to get the classlist
       return if (! defined($classlist));
       &Apache::loncoursedata::get_group_memberships($classlist,$keylist);
       #
       my %sections;
       my %fullnames;
       foreach my $student (keys(%$classlist)) {
           my $end      = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
           my $start    = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
           my $id       = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
           my $section  = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
           my $fullname = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
           my $status   = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
           my $group   = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
    # filter students according to status selected
    if ($filterlist && (!($stu_status =~ /Any/))) {
       if (!($stu_status =~ $status)) {
    delete($classlist->{$student});
    next;
       }
    }
    # filter students according to groups selected
    my @stu_groups = split(/,/,$group);
    if (@getgroup) {
       my $exclude = 1;
       foreach my $grp (@getgroup) {
           foreach my $stu_group (@stu_groups) {
               if ($stu_group eq $grp) {
                   $exclude = 0;
                  } 
           }
              if (($grp eq 'none') && !$group) {
                  $exclude = 0;
           }
       }
       if ($exclude) {
           delete($classlist->{$student});
       }
    }
    $section = ($section ne '' ? $section : 'none');
    if (&canview($section)) {
       if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
    $sections{$section}++;
    if ($classlist->{$student}) {
       $fullnames{$student}=$fullname;
    }
       } else {
    delete($classlist->{$student});
       }
    } else {
       delete($classlist->{$student});
    }
       }
       my %seen = ();
       my @sections = sort(keys(%sections));
       return ($classlist,\@sections,\%fullnames);
 }  }
   
 sub get_last_submission {  sub canmodify {
   my ($symb,$username,$domain,$course)=@_;      my ($sec)=@_;
   if ($symb) {      if ($perm{'mgr'}) {
       my (@string,@timestamp);   if (!defined($perm{'mgr_section'})) {
      my (%returnhash)=&Apache::lonnet::restore($symb,$course,$domain,$username);      # can modify whole class
      if ($returnhash{'version'}) {      return 1;
  my %lasthash=();   } else {
  my ($version);      if ($sec eq $perm{'mgr_section'}) {
  for ($version=1;$version<=$returnhash{'version'};$version++) {   #can modify the requested section
   foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) {   return 1;
       $lasthash{$_}=$returnhash{$version.':'.$_};      } else {
   }   # can't modify the request section
         }   return 0;
  foreach ((keys %lasthash)) {      }
     print "lasthash key=$_<br>";  
     if ($_ =~ /\.submission$/) {push @string, $_,$lasthash{$_}}  
     if ($_ =~ /timestamp/) {push @timestamp, scalar(localtime($lasthash{$_}))};  
 #    if ($_ =~ /\.submission$/) {$string = $lasthash{$_}}  
 #    if ($_ =~ /timestamp/) {$timestamp=scalar(localtime($lasthash{$_}))};  
  }   }
     }      }
      @string = $string[0] eq '' ? 'Nothing submitted - no attempts.' : @string;      #can't modify
      return \@string,\@timestamp;      return 0;
   }  
 }  }
   
 sub keywords_highlight {  sub canview {
   my $string  = shift;      my ($sec)=@_;
   my $size    = $ENV{'form.kwsize'} eq '0' ? '' : 'size='.$ENV{'form.kwsize'};      if ($perm{'vgr'}) {
   my $styleon = $ENV{'form.kwstyle'} eq ''  ? '' : $ENV{'form.kwstyle'};   if (!defined($perm{'vgr_section'})) {
   (my $styleoff = $styleon) =~ s/\</\<\//;      # can modify whole class
   my @keylist = split(/[,\s+]/,$ENV{'form.keywords'});      return 1;
   foreach (@keylist) {   } else {
  #     next if ($_ eq '');      if ($sec eq $perm{'vgr_section'}) {
       $string =~ s/\b$_(\b|\.)/\<font color\=$ENV{'form.kwclr'} $size\>$styleon$_$styleoff\<\/font\>/gi;   #can modify the requested section
   }   return 1;
   return $string;      } else {
    # can't modify the request section
    return 0;
       }
    }
       }
       #can't modify
       return 0;
 }  }
   
 sub processHandGrade {  #--- Retrieve the grade status of a student for all the parts
   my ($request) = shift;  sub student_gradeStatus {
   my $url    = $ENV{'form.url'};      my ($symb,$udom,$uname,$partlist) = @_;
   my $symb   = $ENV{'form.symb'};      my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
   my $button = $ENV{'form.gradeOpt'};      my %partstatus = ();
   my $ngrade = $ENV{'form.NCT'};      foreach (@$partlist) {
   my $ntstu  = $ENV{'form.NTSTU'};   my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
    $status              = 'nothing' if ($status eq '');
   my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};   $partstatus{$_}      = $status;
   my %keyhash = ();   my $subkey           = "resource.$_.submitted_by";
   $ENV{'form.keywords'} =~ s/,\s{0,}|\s+/ /g;   $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
   $keyhash{$symb.'_handgrade'} = 'activated';      }
   $keyhash{$symb.'_keywords'} = $ENV{'form.keywords'};      return %partstatus;
   $keyhash{$symb.'_subject'} = $ENV{'form.msgsub'};  }
   $keyhash{$loginuser.'_kwclr'} = $ENV{'form.kwclr'};  
   $keyhash{$loginuser.'_kwsize'} = $ENV{'form.kwsize'};  
   $keyhash{$loginuser.'_kwstyle'} = $ENV{'form.kwstyle'};  
   
   my ($ctr,$idx) = (1,1);  
   while ($ctr <= $ENV{'form.savemsgN'}) {  
       if ($ENV{'form.savemsg'.$ctr} ne '') {  
   $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.savemsg'.$ctr};  
   $idx++;  
       }  
       $ctr++;  
   }  
   $ctr = 0;  
   while ($ctr < $ngrade) {  
       if ($ENV{'form.newmsg'.$ctr} ne '') {  
   $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};  
   $ENV{'form.savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};  
   $idx++;  
       }  
       $ctr++;  
   }  
   $ENV{'form.savemsgN'} = --$idx;  
   $keyhash{$symb.'_savemsgN'} = $ENV{'form.savemsgN'};  
   my $putresult = &Apache::lonnet::put  
       ('nohist_handgrade',\%keyhash,  
        $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},  
        $ENV{'course.'.$ENV{'request.course.id'}.'.num'});  
   
   my (@parts) = sort(&getpartlist($url));  
   
   if ($button eq 'Save & Next') {  
       my $ctr = 0;  
       while ($ctr < $ngrade) {  
   my ($uname,$udom) = split(/:/,$ENV{'form.unamedom'.$ctr});  
   &saveHandGrade($url,$symb,$uname,$udom,$ctr,@parts);  
   
   my $includemsg = $ENV{'form.includemsg'.$ctr};  
   my ($subject,$message,$msgstatus) = ('','','');  
   if ($includemsg =~ /savemsg|new$ctr/) {  
       $subject = $ENV{'form.msgsub'} if ($includemsg =~ /^msgsub/);  
       my (@msgnum) = split(/,/,$includemsg);  
       foreach (@msgnum) {  
   $message.=$ENV{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');  
       }  
       $message =~ s/\s+/ /g;  
       $msgstatus = &Apache::lonmsg::user_normal_msg ($uname,$udom,$ENV{'form.msgsub'},$message);  
   }  
   if ($ENV{'form.collaborator'.$ctr}) {  
       my (@collaborators) = split(/:/,$ENV{'form.collaborator'.$ctr});  
       foreach (@collaborators) {  
   &saveHandGrade($url,$symb,$_,$udom,$ctr,@parts);  
   if ($message ne '') {  
       $msgstatus = &Apache::lonmsg::user_normal_msg ($uname,$udom,$ENV{'form.msgsub'},$message);  
   }  
       }  
   }  
   $ctr++;  
       }  
   }  
   my $firststu = $ENV{'form.unamedom0'};  
   my $laststu  = $ENV{'form.unamedom'.($ngrade-1)};  
   
   my ($classlist) = &getclasslist($ENV{'form.section'},'0');  # hidden form and javascript that calls the form
   my (@nextlist,@prevlist);  # Use by verifyscript and viewgrades
   my ($nextflg,$prevflg,$ctr,$ctprev) = (0,0,0,0);  # Shows a student's view of problem and submission
   foreach my $student ( sort(@{ $$classlist{$ENV{'form.section'}} }) ) {  sub jscriptNform {
       my ($uname,$udom) = split(/:/,$student);      my ($symb) = @_;
       my ($type,$status) = &student_gradeStatus($ENV{'form.url'},$udom,$uname);      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
       next if ($status eq 'nothing' && $ENV{'form.submitonly'} eq 'yes');      my $jscript='<script type="text/javascript" language="javascript">'."\n".
    '    function viewOneStudent(user,domain) {'."\n".
       if ($nextflg == 1 && $button =~ /Next$/) {   ' document.onestudent.student.value = user;'."\n".
   push @nextlist,$uname if ($ctr < $ntstu);   ' document.onestudent.userdom.value = domain;'."\n".
   $ctr++;   ' document.onestudent.submit();'."\n".
       }   '    }'."\n".
       $nextflg = 1 if ($student eq $laststu);   '</script>'."\n";
       $prevflg = 1 if ($student eq $firststu);      $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
       if ($prevflg == 0 && $button eq 'Previous') {   '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
   push @prevlist,$uname;   '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
   $ctprev++;   '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
       }   '<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
   }   '<input type="hidden" name="command" value="submission" />'."\n".
   if ($button eq 'Previous') {   '<input type="hidden" name="student" value="" />'."\n".
       if ($ctprev <= $ntstu) {   '<input type="hidden" name="userdom" value="" />'."\n".
   @nextlist = @prevlist;   '</form>'."\n";
       } else {      return $jscript;
   my $idx = 0;  
   my $start = $ctprev - $ntstu;  
   while ($idx < $ntstu) {  
       $nextlist[$idx] = $prevlist[$start+$idx];  
       $idx++;  
   }  
       }  
   }  
   $ctr = 0;  
   my $total = scalar(@nextlist)-1;  
   foreach my $student (@nextlist) {  
       $ENV{'form.student'} = $student;  
       &submission($request,$ctr,$total);  
       $ctr++;  
   }  
   if ($total < 0) {  
       my $the_end = '<h3><font color="red">LON-CAPA User Message</font></h3><br />'."\n";  
       $the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n";  
       $the_end.='Click on the button below to return to the grading menu.<br /><br />'."\n";  
       $the_end.=&show_grading_menu_form ($symb,$url);  
       $request->print($the_end);  
   }  
   return '';  
 }  }
   
 sub saveHandGrade {  
   my ($url,$symb,$stuname,$domain,$newflg,@parts) = @_;  
   my %record=&Apache::lonnet::restore($symb,$ENV{'request.course.id'},$domain,$stuname);  # Given the score (as a number [0-1] and the weight) what is the final
   my %newrecord;  # point value? This function will round to the nearest tenth, third,
   # or quarter if one of those is within the tolerance of .00001.
   foreach my $part (@parts) {  sub compute_points {
     my ($temp,$part,$type)=split(/_/,$part);      my ($score, $weight) = @_;
     my $oldscore=$record{"resource.$part.$type"};      
     my $newscore;      my $tolerance = .00001;
     if ($type eq 'awarded' && $newflg >= 0) {      my $points = $score * $weight;
  my $pts    = ($ENV{'form.GRADE_BOX'.$newflg} ne '' ?   
       $ENV{'form.GRADE_BOX'.$newflg} : $ENV{'form.RADVAL'.$newflg});      # Check for nearness to 1/x.
  my $wgt    = $ENV{'form.WGT'.$newflg};      my $check_for_nearness = sub {
 # my $sel    = $ENV{'form.GRADE_SEL'.$newflg};          my ($factor) = @_;
  $newscore  = $pts/$wgt if ($wgt != 0);          my $num = ($points * $factor) + $tolerance;
     }          my $floored_num = floor($num);
     if ($type eq 'solved') {          if ($num - $floored_num < 2 * $tolerance * $factor) {
       $newscore = $ENV{'form.GRADE_SEL'.$newflg} if ($newflg >= 0);              return $floored_num / $factor;
       my $update=0;          }
       if ($newscore eq 'nothing' ) {          return $points;
  if ($oldscore ne '') {      };
   $update=1;  
   $newscore = '';      $points = $check_for_nearness->(10);
  }      $points = $check_for_nearness->(3);
       } elsif ($oldscore !~ m/^$newscore/) {      $points = $check_for_nearness->(4);
  $update=1;      
  if ($newscore eq 'correct')   { $newscore = 'correct_by_override'; }      return $points;
  if ($newscore eq 'incorrect') { $newscore = 'incorrect_by_override'; }  }
  if ($newscore eq 'excused')   { $newscore = 'excused'; }  
  if ($newscore eq 'ungraded')  { $newscore = 'ungraded_attempted'; }  #------------------ End of general use routines --------------------
  if ($newscore eq 'partial')   { $newscore = 'correct_partially_by_override'; }  
       }  #
       if ($update) { $newrecord{"resource.$part.$type"}=$newscore; }  # Find most similar essay
   #
   
   sub most_similar {
       my ($uname,$udom,$uessay,$old_essays)=@_;
   
   # ignore spaces and punctuation
   
       $uessay=~s/\W+/ /gs;
   
   # ignore empty submissions (occuring when only files are sent)
   
       unless ($uessay=~/\w+/) { return ''; }
   
   # these will be returned. Do not care if not at least 50 percent similar
       my $limit=0.6;
       my $sname='';
       my $sdom='';
       my $scrsid='';
       my $sessay='';
   # go through all essays ...
       foreach my $tkey (keys(%$old_essays)) {
    my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
   # ... except the same student
           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);
   # Found one
    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);
     } else {      } else {
       if ($oldscore ne $newscore) {         return ('','','','',0);
  $newrecord{"resource.$part.$type"}=$newscore;  
       }  
     }      }
   }  
   if ( scalar(keys(%newrecord)) > 0 ) {  
       $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";  
 #      while (my ($k,$v) = each %newrecord) {  
 # print "k=$k:v=$v:<br>\n";  
 #      }  
 #      print "symb=$symb,courseid=$ENV{'request.course.id'},dom=$domain,name=$stuname<br>";  
 #     &Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'},$domain,$stuname);  
   }  
   return '';  
 }  }
   
 sub get_symb_and_url {  #-------------------------------------------------------------------
   my ($request) = @_;  
   (my $url=$ENV{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;  
   my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));  
   if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }  
   return ($symb,$url);  
 }  
   
 sub show_grading_menu_form {  #------------------------------------ Receipt Verification Routines
   my ($symb,$url)=@_;  #
   my $result.='<form action="/adm/grades" method="post">'."\n".  #--- Check whether a receipt number is valid.---
     '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".  sub verifyreceipt {
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".      my $request  = shift;
  '<input type="hidden" name="command" value="gradingmenu" />'."\n".  
   '<input type="submit" name="submit" value="Grading Menu" />'."\n".  
     '</form>'."\n";  
   return $result;  
 }  
   
 sub gradingmenu {  
   my ($request) = @_;  
   my ($symb,$url)=&get_symb_and_url($request);  
   if (!$symb) {return '';}  
   my $allkeys = &Apache::lonnet::metadata($url,'keys');  
   my $handgrade = ($allkeys =~ /parameter_.*?_handgrade/ ? 'yes' : 'no');  
   my ($responsetype,$foo) = split(/_/,&Apache::lonnet::metadata($url,'packages'));  
   $allkeys=~s/,/\<br\>/g;  
 #  print "allkeys=$allkeys<br><br>";  
   $Apache::lonxml::debug=1;  
   &Apache::lonxml::debug(join(':',&Apache::lonnet::metadata($url,'packages')));  
   $Apache::lonxml::debug=0;  
   
   my $result='<h2>&nbsp;<font color="#339933">Select a Grading Method</font></h2>';  
   $result.='<table border="0">';  
   $result.='<tr><td><font size=+1><b>Resource: </b></font></td>'.  
       '<td><font size=+1>'.$url.'</font></td></tr>';  
   $result.='<tr><td><font size=+1><b>Type: </b></font></td>'.  
       '<td><font size=+1>'.$responsetype.'&nbsp;&nbsp;<b>Handgrade: </b>'.$handgrade.'</font></td></tr>';  
   $result.='</table>';  
   my $t0=&Time::HiRes::time();  
   $result.=&view_edit_entire_class_form($symb,$url).'<br />';  
   my $t1=&Time::HiRes::time();  
   print "elapsed10=",$t1-$t0,"<br />";  
   $result.=&upcsvScores_form($symb,$url).'<br />';  
   my $t2=&Time::HiRes::time();  
   print "elapsed21=",$t2-$t1,"<br />";  
   $result.=&viewGradeaStu_form($symb,$url,$responsetype,$handgrade).'<br />';  
   my $t3=&Time::HiRes::time();  
   print "elapsed32=",$t3-$t2,"<br />";  
   $result.=&verifyReceipt_form($symb,$url);  
   my $t4=&Time::HiRes::time();  
   print "elapsed43=",$t4-$t3,"<br />";  
   print "Total elapsed time=",$t4-$t0,"<br />";  
   return $result;  
 }  
   
 sub view_edit_entire_class_form {  
   my ($symb,$url)=@_;  
   my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";  
   $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";  
   $result.='&nbsp;<b>View/Grade Entire Class</b></td></tr>'."\n";  
   $result.='<tr bgcolor=#ffffe6><td>'."\n";  
   $result.='<form action="/adm/grades" method="post">'."\n".  
     '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".  
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".  
   '<input type="hidden" name="command" value="viewgrades" />'."\n";  
   $result.='&nbsp;<b>Display students who has: </b>'.  
       '<input type="radio" name="submitonly" value="yes" checked> submitted'.  
   '<input type="radio" name="submitonly" value="all"> everybody <br /><br />';  
   $result.='&nbsp;<input type="submit" name="submit" value="View/Grade" /></form>'."\n";  
   $result.='</td></tr></table>'."\n";  
   $result.='</td></tr></table>'."\n";  
   return $result;  
 }  
   
 sub upcsvScores_form {      my $courseid = $env{'request.course.id'};
   my ($symb,$url) = @_;      my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
   if (!$symb) {return '';}   $env{'form.receipt'};
   my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";      $receipt     =~ s/[^\-\d]//g;
   $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";      my ($symb)   = &get_symb($request);
   $result.='&nbsp;<b>Specify a file containing the class scores for above resource</b></td></tr>'."\n";  
   $result.='<tr bgcolor=#ffffe6><td>'."\n";      my $title.=
   my $upfile_select=&Apache::loncommon::upfile_select_html();   '<h3><span class="LC_info">'.
   $result.=<<ENDUPFORM;   &mt('Verifying Submission Receipt [_1]',$receipt).
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">   '</span></h3>'."\n".
 <input type="hidden" name="symb" value="$symb" />   '<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}).
 <input type="hidden" name="url" value="$url" />   '</h4>'."\n";
 <input type="hidden" name="command" value="csvuploadmap" />  
 $upfile_select      my ($string,$contents,$matches) = ('','',0);
 <br />&nbsp;<input type="submit" name="submit" value="Upload Grades" />      my (undef,undef,$fullname) = &getclasslist('all','0');
 </form>      
 ENDUPFORM      my $receiptparts=0;
   $result.='</td></tr></table>'."\n";      if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
   $result.='</td></tr></table>'."\n";   $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
   return $result;      my $parts=['0'];
       if ($receiptparts) { ($parts)=&response_type($symb); }
       
       my $header = 
    &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th>&nbsp;'.&mt('Fullname').'&nbsp;</th>'."\n".
    '<th>&nbsp;'.&mt('Username').'&nbsp;</th>'."\n".
    '<th>&nbsp;'.&mt('Domain').'&nbsp;</th>';
       if ($receiptparts) {
    $header.='<th>&nbsp;'.&mt('Problem Part').'&nbsp;</th>';
       }
       $header.=
    &Apache::loncommon::end_data_table_header_row();
   
       foreach (sort 
        {
    if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
        return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
    }
    return $a cmp $b;
        } (keys(%$fullname))) {
    my ($uname,$udom)=split(/\:/);
    foreach my $part (@$parts) {
       if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
    $contents.=
       &Apache::loncommon::start_data_table_row().
       '<td>&nbsp;'."\n".
       '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
       '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
       '<td>&nbsp;'.$uname.'&nbsp;</td>'.
       '<td>&nbsp;'.$udom.'&nbsp;</td>';
    if ($receiptparts) {
       $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
    }
    $contents.= 
       &Apache::loncommon::end_data_table_row()."\n";
   
    $matches++;
       }
    }
       }
       if ($matches == 0) {
    $string = $title.&mt('No match found for the above receipt.');
       } else {
    $string = &jscriptNform($symb).$title.
       '<p>'.
       &mt('The above receipt matches the following [numerate,_1,student].',$matches).
       '</p>'.
       $header.
       $contents.
       &Apache::loncommon::end_data_table()."\n";
       }
       return $string.&show_grading_menu_form($symb);
 }  }
   
 sub viewGradeaStu_form {  #--- This is called by a number of programs.
   my ($symb,$url,$response,$handgrade) = @_;  #--- Called from the Grading Menu - View/Grade an individual student
   #--- Also called directly when one clicks on the subm button 
   #    on the problem page.
   sub listStudents {
       my ($request) = shift;
   
       my ($symb) = &get_symb($request);
       my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
       my $cnum      = $env{"course.$env{'request.course.id'}.num"};
       my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
       my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
       my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
       my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
       $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
    &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
   
       my $result='<h3><span class="LC_info">&nbsp;'.
    &mt($viewgrade.' Submissions for a Student or a Group of Students')
    .'</span></h3>';
   
       my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
   
       my %lt = ( '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.",
          );
       %lt = &Apache::lonlocal::texthash(%lt);
       $request->print(<<LISTJAVASCRIPT);
   <script type="text/javascript" language="javascript">
       function checkSelect(checkBox) {
    var ctr=0;
    var sense="";
    if (checkBox.length > 1) {
       for (var i=0; i<checkBox.length; i++) {
    if (checkBox[i].checked) {
       ctr++;
    }
       }
       sense = '$lt{'multiple'}';
    } else {
       if (checkBox.checked) {
    ctr = 1;
       }
       sense = '$lt{'single'}';
    }
    if (ctr == 0) {
       alert(sense);
       return false;
    }
    document.gradesub.submit();
       }
   
   my $t3=&Time::HiRes::time();      function reLoadList(formname) {
    if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
    formname.command.value = 'submission';
    formname.submit();
       }
   </script>
   LISTJAVASCRIPT
   
   my ($classlist,$sections) = &getclasslist('all','0');      &commonJSfunctions($request);
       $request->print($result);
   
   my $t4=&Time::HiRes::time();      my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';
   print "elapsed_getclasslist=",$t4-$t3,"<br />";      my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';
       my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
    "\n".$table;
   
       $gradeTable .= 
    '&nbsp;'.
    &mt('<b>View Problem Text: </b>[_1]',
       '<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;'.
    &mt('<b>View Answer: </b>[_1]',
       '<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";
   
       my $submission_options;
       if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
    $submission_options.=
       '<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> '.&mt('essay part only').' </label>'."\n";
       }
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
       my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
       $env{'form.Status'} = $saveStatus;
       $submission_options.=
    '<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> '.&mt('last submission only').' </label>'."\n".
    '<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;'.
    &mt('<b>Submissions: </b>[_1]',$submission_options).'<br />'."\n";
   
       $gradeTable .= 
           '&nbsp;'.
    &mt('<b>Grading Increments:</b> [_1]',
       '<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".
    '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
    '<input type="hidden" name="saveState"   value="'.$env{'form.saveState'}.'" />'."\n".
    '<input type="hidden" name="probTitle"   value="'.$env{'form.probTitle'}.'" />'."\n".
    '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
   
       if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
    $gradeTable.='<input type="hidden" name="Status"   value="'.$stu_status.'" />'."\n";
       } else {
    $gradeTable.=&mt('<b>Student Status:</b> [_1]',
    &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);')).'<br />';
       }
   
   my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";      $gradeTable.=&mt('To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '.
   $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";   'next to the student\'s name(s). Then click on the Next button.').'<br />'."\n".
   $result.='&nbsp;<b>View/Grade an Individual Student\'s Submission</b></td></tr>'."\n";   '<input type="hidden" name="command" value="processGroup" />'."\n";
   $result.='<tr bgcolor=#ffffe6><td>'."\n";  
   $result.='<form action="/adm/grades" method="post">'."\n".  # checkall buttons
      '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".      $gradeTable.=&check_script('gradesub', 'stuinfo');
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".      $gradeTable.='<input type="button" '."\n".
        '<input type="hidden" name="response" value="'.$response.'" />'."\n".   'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
         '<input type="hidden" name="handgrade" value="'.$handgrade.'" />'."\n".   'value="'.&mt('Next-&gt;').'" /> <br />'."\n";
  '<input type="hidden" name="command" value="submission" />'."\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();
       my $loop = 0;
       while ($loop < 2) {
    $gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
       '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
    if ($env{'form.showgrading'} eq 'yes' 
       && $submitonly ne 'queued'
       && $submitonly ne 'all') {
       foreach my $part (sort(@$partlist)) {
    my $display_part=
       &get_display_part((split(/_/,$part))[0],$symb);
    $gradeTable.=
       '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
       }
    } elsif ($submitonly eq 'queued') {
       $gradeTable.='<th>'.&mt('Queue Status').'&nbsp;</th>';
    }
    $loop++;
   # $gradeTable.='<td></td>' if ($loop%2 ==1);
       }
       $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n";
   
   $result.='&nbsp;<b>Select section:</b> <select name="section">'."\n";      my $ctr = 0;
   foreach my $section (sort (@$sections)) {      foreach my $student (sort 
       $result.= '<option>'.$section.'</option>'."\n";   {
   }       if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
   $result.= '<option selected="on">all</select>'."\n";   return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
   $result.='&nbsp;&nbsp;<b>Display students who has: </b>'.       }
       '<input type="radio" name="submitonly" value="yes" checked> submitted'.       return $a cmp $b;
   '<input type="radio" name="submitonly" value="all"> everybody <br />';   }
   $result.='&nbsp;(Section "no" implies the students were not assigned a section.)<br />'    (keys(%$fullname))) {
       if (grep /no/,@$sections);   my ($uname,$udom) = split(/:/,$student);
   
   $result.='<br />&nbsp;<input type="submit" name="submit" value="View/Grade" />'."\n".   my %status = ();
       '</form>'."\n";  
   $result.='</td></tr></table>'."\n";   if ($submitonly eq 'queued') {
   $result.='</td></tr></table>'."\n";      my %queue_status = 
   return $result;   &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
 }   $udom,$uname);
       next if (!defined($queue_status{'gradingqueue'}));
 sub verifyReceipt_form {      $status{'gradingqueue'} = $queue_status{'gradingqueue'};
   my ($symb,$url) = @_;   }
   my $cdom=$ENV{"course.$ENV{'request.course.id'}.domain"};  
   my $cnum=$ENV{"course.$ENV{'request.course.id'}.num"};  
   my $hostver=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'});  
   
   my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";  
   $result.='<table width=100% border=0><tr><td bgcolor=#e6ffff>'."\n";  
   $result.='&nbsp;<b>Verify a Submission Receipt Issued by this Server</td></tr>'."\n";  
   $result.='<tr bgcolor=#ffffe6><td>'."\n";  
   $result.='<form action="/adm/grades" method="post">'."\n";  
   $result.='&nbsp;<tt>'.$hostver.'-<input type="text" name="receipt" size="4"></tt><br />'."\n";  
   $result.='&nbsp;<input type="submit" name="submit" value="Verify Receipt">'."\n";  
   $result.='<input type="hidden" name="command" value="verify">'."\n";  
   if ($ENV{'form.url'}) {  
       $result.='<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />';  
   }  
   if ($ENV{'form.symb'}) {  
       $result.='<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />';  
   }  
   $result.='</form>';  
   $result.='</td></tr></table>'."\n";  
   $result.='</td></tr></table>'."\n";  
   return $result;  
 }  
   
 sub viewgrades {   if ($env{'form.showgrading'} eq 'yes' 
   my ($request) = @_;      && $submitonly ne 'queued'
   my $result='';      && $submitonly ne 'all') {
       (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
       my $submitted = 0;
       my $graded = 0;
       my $incorrect = 0;
       foreach (keys(%status)) {
    $submitted = 1 if ($status{$_} ne 'nothing');
    $graded = 1 if ($status{$_} =~ /^ungraded/);
    $incorrect = 1 if ($status{$_} =~ /^incorrect/);
   
    my ($foo,$partid,$foo1) = split(/\./,$_);
    if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
       $submitted = 0;
       my ($part)=split(/\./,$partid);
       $gradeTable.='<input type="hidden" name="'.
    $student.':'.$part.':submitted_by" value="'.
    $status{'resource.'.$partid.'.submitted_by'}.'" />';
    }
       }
       
       next if (!$submitted && ($submitonly eq 'yes' ||
        $submitonly eq 'incorrect' ||
        $submitonly eq 'graded'));
       next if (!$graded && ($submitonly eq 'graded'));
       next if (!$incorrect && $submitonly eq 'incorrect');
    }
   
   #get resource reference   $ctr++;
   my ($symb,$url)=&get_symb_and_url($request);   my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
   if (!$symb) {return '';}          my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
   #get classlist   if ( $perm{'vgr'} eq 'F' ) {
   my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'});      if ($ctr%2 ==1) {
   #print "Found $cdom:$cnum<br />";   $gradeTable.= &Apache::loncommon::start_data_table_row();
   my ($classlist) = &getclasslist('all','0');      }
   my $headerclr = '"#ddffff"';      $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
   my $cellclr = '"#ffffdd"';                 '<td align="center"><label><input type=checkbox name="stuinfo" value="'.
                  $student.':'.$$fullname{$student}.':::SECTION'.$section.
   #get list of parts for this problem         ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
   my (@parts) = sort(&getpartlist($url));         &nameUserString(undef,$$fullname{$student},$uname,$udom).
          '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
   $request->print ("<h2><font color=\"#339933\">Manual Grading</font></h2>");  
       if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
   #start the form   foreach (sort keys(%status)) {
   $result = '<form action="/adm/grades" method="post">'."\n".      next if ($_ =~ /^resource.*?submitted_by$/);
     '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".      $gradeTable.='<td align="center">&nbsp;'.&mt($status{$_}).'&nbsp;</td>'."\n";
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".   }
  '<input type="hidden" name="command" value="editgrades" />'."\n".      }
   '<input type="submit" name="submit" value="Submit Changes" />'."\n".  #    $gradeTable.='<td></td>' if ($ctr%2 ==1);
     '<table border=0><tr><td bgcolor="#777777">'."\n".      if ($ctr%2 ==0) {
      '<table border=0>'."\n".   $gradeTable.=&Apache::loncommon::end_data_table_row()."\n";
       '<tr bgcolor='.$headerclr.'><td><b>Username</b></td><td><b>Fullname</b></td><td><b>Domain</b></td>'."\n";      }
   foreach my $part (@parts) {   }
      my $display=&Apache::lonnet::metadata($url,$part.'.display');      }
      if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }      if ($ctr%2 ==1) {
      $result.='<td><b>'.$display.'</b></td>'."\n";   $gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
   }      if ($env{'form.showgrading'} eq 'yes' 
   $result.='</tr>';   && $submitonly ne 'queued'
   #get info for each student   && $submitonly ne 'all') {
   foreach my $student ( sort(@{ $$classlist{'all'} }) ) {   foreach (@$partlist) {
 #    my $display=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},$student,@parts);      $gradeTable.='<td>&nbsp;</td>';
 #      print "ID=$ENV{'request.course.id'}:STU=$student:DIS=$display:<br>\n";   }
     $result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},$student,@parts);      } elsif ($submitonly eq 'queued') {
   }   $gradeTable.='<td>&nbsp;</td>';
   $result.='</table></td></tr></table>';      }
   $result.='<input type="submit" name="submit" value="Submit Changes" /></form>';   $gradeTable.=&Apache::loncommon::end_data_table_row();
   $result.=&show_grading_menu_form($symb,$url);      }
   return $result;  
       $gradeTable.=&Apache::loncommon::end_data_table()."\n".
    '<input type="button" '.
    'onClick="javascript:checkSelect(this.form.stuinfo);" '.
    'value="'.&mt('Next-&gt;').'" /></form>'."\n";
       if ($ctr == 0) {
    my $num_students=(scalar(keys(%$fullname)));
    if ($num_students eq 0) {
       $gradeTable='<br />&nbsp;<span class="LC_warning">'.&mt('There are no students currently enrolled.').'</span>';
    } else {
       my $submissions='submissions';
       if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
       if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
       if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
       $gradeTable='<br />&nbsp;<span class="LC_warning">'.
    &mt('No '.$submissions.' found for this resource for any students. ([_1] students checked for '.$submissions.')',
       $num_students).
    '</span><br />';
    }
       } elsif ($ctr == 1) {
    $gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/;
       }
       $gradeTable.=&show_grading_menu_form($symb);
       $request->print($gradeTable);
       return '';
 }  }
   
 sub editgrades {  #---- Called from the listStudents routine
   my ($request) = @_;  
   my $result='';  
   
   my $symb=$ENV{'form.symb'};  sub check_script {
   if ($symb eq '') { $request->print("Unable to handle ambiguous references:$symb:$ENV{'form.url'}"); return ''; }      my ($form, $type)=@_;
   my $url=$ENV{'form.url'};      my $chkallscript='<script type="text/javascript">
   #get classlist      function checkall() {
 #  my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'});          for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
   #print "Found $cdom:$cnum<br />";              ele = document.forms.'.$form.'.elements[i];
   my ($classlist) = &getclasslist('all','0');              if (ele.name == "'.$type.'") {
               document.forms.'.$form.'.elements[i].checked=true;
   #get list of parts for this problem                                         }
   my (@parts) = &getpartlist($url);          }
       }
   $result.='<form action="/adm/grades" method="post">'."\n".  
     '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".  
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".  
  '<input type="hidden" name="command" value="viewgrades" />'."\n".  
   '<input type="submit" name="submit" value="See Grades" /> <br />'."\n";  
   
   foreach my $student ( sort(@{ $$classlist{'all'} }) ) {      function checksec() {
     $result.=&setstudentgrade($url,$symb,$ENV{'request.course.id'},$student,@parts);          for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
   }              ele = document.forms.'.$form.'.elements[i];
              string = document.forms.'.$form.'.chksec.value;
              if
             (ele.value.indexOf(":::SECTION"+string)>0) {
                 document.forms.'.$form.'.elements[i].checked=true;
               }
           }
       }
   
   
       function uncheckall() {
           for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
               ele = document.forms.'.$form.'.elements[i];
               if (ele.name == "'.$type.'") {
               document.forms.'.$form.'.elements[i].checked=false;
                                          }
           }
       }
   
   $result.='<input type="submit" name="submit" value="See Grades" /></table></form>';  </script>'."\n";
   return $result;      return $chkallscript;
 }  }
   
   sub check_buttons {
       my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
       $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" />&nbsp;';
       $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
       $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
       return $buttons;
   }
   
   #     Displays the submissions for one student or a group of students
   sub processGroup {
       my ($request)  = shift;
       my $ctr        = 0;
       my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
       my $total      = scalar(@stuchecked)-1;
   
       foreach my $student (@stuchecked) {
    my ($uname,$udom,$fullname) = split(/:/,$student);
    $env{'form.student'}        = $uname;
    $env{'form.userdom'}        = $udom;
    $env{'form.fullname'}       = $fullname;
    &submission($request,$ctr,$total);
    $ctr++;
       }
       return '';
   }
   
   #------------------------------------------------------------------------------------
   #
   #-------------------------- Next few routines handles grading by student, essentially
   #                           handles essay response type problem/part
   #
   #--- Javascript to handle the submission page functionality ---
 sub sub_page_js {  sub sub_page_js {
   my $request = shift;      my $request = shift;
   $request->print(<<SUBJAVASCRIPT);      $request->print(<<SUBJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
   function updateRadio(radioButton,formtextbox,formsel,wgt) {      function updateRadio(formname,id,weight) {
      var pts = formtextbox.value;   var gradeBox = formname["GD_BOX"+id];
      var resetbox =false;   var radioButton = formname["RADVAL"+id];
      if (isNaN(pts) || pts < 0) {   var oldpts = formname["oldpts"+id].value;
  alert("A number equal or greater than 0 is expected. Entered value = "+pts);   var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
    gradeBox.value = pts;
    var resetbox = false;
    if (isNaN(pts) || pts < 0) {
       alert("A number equal or greater than 0 is expected. Entered value = "+pts);
       for (var i=0; i<radioButton.length; i++) {
    if (radioButton[i].checked) {
       gradeBox.value = i;
       resetbox = true;
    }
       }
       if (!resetbox) {
    formtextbox.value = "";
       }
       return;
    }
   
    if (pts > weight) {
       var resp = confirm("You entered a value ("+pts+
          ") greater than the weight for the part. Accept?");
       if (resp == false) {
    gradeBox.value = oldpts;
    return;
       }
    }
   
  for (var i=0; i<radioButton.length; i++) {   for (var i=0; i<radioButton.length; i++) {
    if (radioButton[i].checked) {      radioButton[i].checked=false;
       formtextbox.value = i;      if (pts == i && pts != "") {
       resetbox = true;   radioButton[i].checked=true;
    }      }
  }   }
  if (!resetbox) {   updateSelect(formname,id);
    formtextbox.value = "";   formname["stores"+id].value = "0";
       }
   
       function writeBox(formname,id,pts) {
    var gradeBox = formname["GD_BOX"+id];
    if (checkSolved(formname,id) == 'update') {
       gradeBox.value = pts;
    } else {
       var oldpts = formname["oldpts"+id].value;
       gradeBox.value = oldpts;
       var radioButton = formname["RADVAL"+id];
       for (var i=0; i<radioButton.length; i++) {
    radioButton[i].checked=false;
    if (i == oldpts) {
       radioButton[i].checked=true;
    }
       }
  }   }
    formname["stores"+id].value = "0";
    updateSelect(formname,id);
  return;   return;
     }      }
   
     for (var i=0; i<radioButton.length; i++) {      function clearRadBox(formname,id) {
  radioButton[i].checked=false;   if (checkSolved(formname,id) == 'noupdate') {
  if (pts == i) {      updateSelect(formname,id);
    radioButton[i].checked=true;      return;
  }   }
    gradeSelect = formname["GD_SEL"+id];
    for (var i=0; i<gradeSelect.length; i++) {
       if (gradeSelect[i].selected) {
    var selectx=i;
       }
    }
    var stores = formname["stores"+id];
    if (selectx == stores.value) { return };
    var gradeBox = formname["GD_BOX"+id];
    gradeBox.value = "";
    var radioButton = formname["RADVAL"+id];
    for (var i=0; i<radioButton.length; i++) {
       radioButton[i].checked=false;
    }
    stores.value = selectx;
     }      }
     updateSelect(formsel,pts,wgt);  
   }  
   
   function writeBox(formrad,formsel,pts,wgt) {      function checkSolved(formname,id) {
     formrad.value = pts;   if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') {
     updateSelect(formsel,pts,wgt);      var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?");
     return;      if (!reply) {return "noupdate";}
   }      formname.overRideScore.value = 'yes';
    }
    return "update";
       }
   
       function updateSelect(formname,id) {
    formname["GD_SEL"+id][0].selected = true;
    return;
       }
   
   function updateSelect(formsel,pts,wgt) {  //=========== Check that a point is assigned for all the parts  ============
     if (pts == 0) {      function checksubmit(formname,val,total,parttot) {
       formsel[1].selected = true;   formname.gradeOpt.value = val;
    if (val == "Save & Next") {
       for (i=0;i<=total;i++) {
    for (j=0;j<parttot;j++) {
       var partid = formname["partid"+i+"_"+j].value;
       if (formname["GD_SEL"+i+"_"+partid][0].selected) {
    var points = formname["GD_BOX"+i+"_"+partid].value;
    if (points == "") {
       var name = formname["name"+i].value;
       var studentID = (name != '' ? name : formname["unamedom"+i].value);
       var resp = confirm("You did not assign a score for "+studentID+
          ", part "+partid+". Continue?");
       if (resp == false) {
    formname["GD_BOX"+i+"_"+partid].focus();
    return false;
       }
    }
       }
       
    }
       }
       
    }
    if (val == "Grade Student") {
       formname.showgrading.value = "yes";
       if (formname.Status.value == "") {
    formname.Status.value = "Active";
       }
       formname.studentNo.value = total;
    }
    formname.submit();
     }      }
     if (pts > 0 && pts < wgt) {  
       formsel[4].selected = true;  //======= Check that a score is assigned for all the problems (page/sequence grading only) =========
       function checkSubmitPage(formname,total) {
    noscore = new Array(100);
    var ptr = 0;
    for (i=1;i<total;i++) {
       var partid = formname["q_"+i].value;
       if (formname["GD_SEL"+i+"_"+partid][0].selected) {
    var points = formname["GD_BOX"+i+"_"+partid].value;
    var status = formname["solved"+i+"_"+partid].value;
    if (points == "" && status != "correct_by_student") {
       noscore[ptr] = i;
       ptr++;
    }
       }
    }
    if (ptr != 0) {
       var sense = ptr == 1 ? ": " : "s: ";
       var prolist = "";
       if (ptr == 1) {
    prolist = noscore[0];
       } else {
    var i = 0;
    while (i < ptr-1) {
       prolist += noscore[i]+", ";
       i++;
    }
    prolist += "and "+noscore[i];
       }
       var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?");
       if (resp == false) {
    return false;
       }
    }
   
    formname.submit();
       }
   </script>
   SUBJAVASCRIPT
   }
   
   #--- javascript for essay type problem --
   sub sub_page_kw_js {
       my $request = shift;
       my $iconpath = $request->dir_config('lonIconsURL');
       &commonJSfunctions($request);
   
       my $inner_js_msg_central=<<INNERJS;
       <script text="text/javascript">
       function checkInput() {
         opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
         var nmsg   = opener.document.SCORE.savemsgN.value;
         var usrctr = document.msgcenter.usrctr.value;
         var newval = opener.document.SCORE["newmsg"+usrctr];
         newval.value = opener.checkEntities(document.msgcenter.newmsg.value);
   
         var msgchk = "";
         if (document.msgcenter.subchk.checked) {
            msgchk = "msgsub,";
         }
         var includemsg = 0;
         for (var i=1; i<=nmsg; i++) {
             var opnmsg = opener.document.SCORE["savemsg"+i];
             var frmmsg = document.msgcenter["msg"+i];
             opnmsg.value = opener.checkEntities(frmmsg.value);
             var showflg = opener.document.SCORE["shownOnce"+i];
             showflg.value = "1";
             var chkbox = document.msgcenter["msgn"+i];
             if (chkbox.checked) {
                msgchk += "savemsg"+i+",";
                includemsg = 1;
             }
         }
         if (document.msgcenter.newmsgchk.checked) {
            msgchk += "newmsg"+usrctr;
            includemsg = 1;
         }
         imgformname = opener.document.SCORE["mailicon"+usrctr];
         imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif");
         var includemsg = opener.document.SCORE["includemsg"+usrctr];
         includemsg.value = msgchk;
   
         self.close()
   
     }      }
     if (pts == wgt) {      </script>
       formsel[0].selected = true;  INNERJS
   
       my $inner_js_highlight_central=<<INNERJS;
    <script type="text/javascript">
       function updateChoice(flag) {
         opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
         opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
         opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);
         opener.document.SCORE.refresh.value = "on";
         if (opener.document.SCORE.keywords.value!=""){
            opener.document.SCORE.submit();
         }
         self.close()
       }
   </script>
   INNERJS
   
       my $start_page_msg_central = 
           &Apache::loncommon::start_page('Message Central',$inner_js_msg_central,
          {'js_ready'  => 1,
    'only_body' => 1,
    'bgcolor'   =>'#FFFFFF',});
       my $end_page_msg_central = 
    &Apache::loncommon::end_page({'js_ready' => 1});
   
   
       my $start_page_highlight_central = 
           &Apache::loncommon::start_page('Highlight Central',
          $inner_js_highlight_central,
          {'js_ready'  => 1,
    'only_body' => 1,
    'bgcolor'   =>'#FFFFFF',});
       my $end_page_highlight_central = 
    &Apache::loncommon::end_page({'js_ready' => 1});
   
       my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
       $docopen=~s/^document\.//;
       $request->print(<<SUBJAVASCRIPT);
   <script type="text/javascript" language="javascript">
   
   //===================== Show list of keywords ====================
     function keywords(formname) {
       var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value);
       if (nret==null) return;
       formname.keywords.value = nret;
   
       if (formname.keywords.value != "") {
    formname.refresh.value = "on";
    formname.submit();
     }      }
     return;      return;
   }    }
   
   function keywords(keyform) {  //===================== Script to view submitted by ==================
     var keywds = keyform.value;    function viewSubmitter(submitter) {
     var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",keywds);      document.SCORE.refresh.value = "on";
     if (nret==null) return;      document.SCORE.NCT.value = "1";
     keyform.value = nret;      document.SCORE.unamedom0.value = submitter;
       document.SCORE.submit();
     return;      return;
   }    }
   
Line 1087  sub sub_page_js { Line 1440  sub sub_page_js {
     else return;      else return;
     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");      var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
     if (cleantxt=="") {      if (cleantxt=="") {
  alert("Select a word or group of words from document and then click this link.");   alert("Please select a word or group of words from document and then click this link.");
  return;   return;
     }      }
     var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);      var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
     if (nret==null) return;      if (nret==null) return;
     var curlist = document.SCORE.keywords.value;      document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
     document.SCORE.keywords.value = curlist+" "+nret;      if (document.SCORE.keywords.value != "") {
    document.SCORE.refresh.value = "on";
    document.SCORE.submit();
       }
     return;      return;
   }    }
   
 //====================== Script for composing message ==============  //====================== Script for composing message ==============
      // preload images
      img1 = new Image();
      img1.src = "$iconpath/mailbkgrd.gif";
      img2 = new Image();
      img2.src = "$iconpath/mailto.gif";
   
   function msgCenter(msgform,usrctr,fullname) {    function msgCenter(msgform,usrctr,fullname) {
     var Nmsg  = msgform.savemsgN.value;      var Nmsg  = msgform.savemsgN.value;
     savedMsgHeader(Nmsg,usrctr,fullname);      savedMsgHeader(Nmsg,usrctr,fullname);
     var subject = msgform.msgsub.value;      var subject = msgform.msgsub.value;
     var rtrchk  = eval("document.SCORE.includemsg"+usrctr);      var msgchk = document.SCORE["includemsg"+usrctr].value;
     var msgchk = rtrchk.value;  
 //    alert("checked=>"+msgchk);  
     re = /msgsub/;      re = /msgsub/;
     var shwsel = "";      var shwsel = "";
     if (re.test(msgchk)) { shwsel = "checked" }      if (re.test(msgchk)) { shwsel = "checked" }
     displaySubject(subject,shwsel);      subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject);
       displaySubject(checkEntities(subject),shwsel);
     for (var i=1; i<=Nmsg; i++) {      for (var i=1; i<=Nmsg; i++) {
  var testpt = "savemsg"+i+",";   var testmsg = "savemsg"+i+",";
  re = /testpt/;   re = new RegExp(testmsg,"g");
  shwsel = "";   shwsel = "";
  if (re.test(msgchk)) { shwsel = "checked" }   if (re.test(msgchk)) { shwsel = "checked" }
  var message = eval("document.SCORE.savemsg"+i+".value");   var message = document.SCORE["savemsg"+i].value;
  displaySavedMsg(i,message,shwsel);   message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message);
    displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages,
                                      //any &lt; is already converted to <, etc. However, only once!!
     }      }
     newmsg = eval("document.SCORE.newmsg"+usrctr+".value");      newmsg = document.SCORE["newmsg"+usrctr].value;
     shwsel = "";      shwsel = "";
     re = /newmsg/;      re = /newmsg/;
     if (re.test(msgchk)) { shwsel = "checked" }      if (re.test(msgchk)) { shwsel = "checked" }
Line 1126  sub sub_page_js { Line 1489  sub sub_page_js {
     return;      return;
   }    }
   
     function checkEntities(strx) {
       if (strx.length == 0) return strx;
       var orgStr = ["&", "<", ">", '"']; 
       var newStr = ["&amp;", "&lt;", "&gt;", "&quot;"];
       var counter = 0;
       while (counter < 4) {
    strx = strReplace(strx,orgStr[counter],newStr[counter]);
    counter++;
       }
       return strx;
     }
   
     function strReplace(strx, orgStr, newStr) {
       return strx.split(orgStr).join(newStr);
     }
   
   function savedMsgHeader(Nmsg,usrctr,fullname) {    function savedMsgHeader(Nmsg,usrctr,fullname) {
     var height = 30*Nmsg+250;      var height = 70*Nmsg+250;
     var scrollbar = "no";      var scrollbar = "no";
     if (height > 600) {      if (height > 600) {
  height = 600;   height = 600;
  scrollbar = "yes";   scrollbar = "yes";
     }      }
 /*    if (window.pWin)      var xpos = (screen.width-600)/2;
  window.pWin.close(); */      xpos = (xpos < 0) ? '0' : xpos;
     pWin = window.open('', 'MessageCenter', 'toolbar=no,location=no,scrollbars='+scrollbar+',screenx=70,screeny=75,width=600,height='+height);      var ypos = (screen.height-height)/2-30;
     pWin.document.write("<html><head>");      ypos = (ypos < 0) ? '0' : ypos;
     pWin.document.write("<title>Message Central</title>");  
       pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
     pWin.document.write("<script language=javascript>");      pWin.focus();
     pWin.document.write("function checkInput() {");      pDoc = pWin.document;
     pWin.document.write("  opener.document.SCORE.msgsub.value = document.msgcenter.msgsub.value;");      pDoc.$docopen;
     pWin.document.write("  var nmsg   = opener.document.SCORE.savemsgN.value;");      pDoc.write('$start_page_msg_central');
     pWin.document.write("  var usrctr = document.msgcenter.usrctr.value;");  
     pWin.document.write("  var newval = eval(\\"opener.document.SCORE.newmsg\\"+usrctr);");      pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
     pWin.document.write("  newval.value = document.msgcenter.newmsg.value;");      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 />");
     pWin.document.write("  var msgchk = \\"\\";");  
     pWin.document.write("  if (document.msgcenter.subchk.checked) {");      pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
     pWin.document.write("     msgchk = \\"msgsub,\\";");      pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
     pWin.document.write("  }");      pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>");
     pWin.document.write(   "for (var i=1; i<=nmsg; i++) {");  
     pWin.document.write("      var opnmsg = eval(\\"opener.document.SCORE.savemsg\\"+i);");  
     pWin.document.write("      var frmmsg = eval(\\"document.msgcenter.msg\\"+i);");  
     pWin.document.write("      opnmsg.value = frmmsg.value;");  
     pWin.document.write("      var chkbox = eval(\\"document.msgcenter.msgn\\"+i);");  
     pWin.document.write("      if (chkbox.checked) {");  
     pWin.document.write("         msgchk += \\"savemsg\\"+i+\\",\\";");  
     pWin.document.write("      }");  
     pWin.document.write("  }");  
     pWin.document.write("  if (document.msgcenter.newmsgchk.checked) {");  
     pWin.document.write("     msgchk += \\"newmsg\\"+usrctr;");  
     pWin.document.write("  }");  
     pWin.document.write("  var includemsg = eval(\\"opener.document.SCORE.includemsg\\"+usrctr);");  
     pWin.document.write("  includemsg.value = msgchk;");  
   
 //    pWin.document.write("  alert(\\"slected=\\"+msgchk)");  
     pWin.document.write("  self.close()");  
   
     pWin.document.write("}");  
   
     pWin.document.write("<");  
     pWin.document.write("/script>");  
   
     pWin.document.write("</head><body bgcolor=white>");  
   
     pWin.document.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");  
     pWin.document.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");  
     pWin.document.write("<font color=\\"green\\" size=+1>&nbsp;Compose Message for \"+fullname+\"</font><br><br>");  
   
     pWin.document.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");  
     pWin.document.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");  
     pWin.document.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>");  
 }  }
     function displaySubject(msg,shwsel) {      function displaySubject(msg,shwsel) {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc = pWin.document;
     pWin.document.write("<td>Subject</td>");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>");      pDoc.write("<td>Subject<\\/td>");
     pWin.document.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+" \\"size=\\"60\\" maxlength=\\"80\\"></td></tr>");      pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
       pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"><\\/td><\\/tr>");
 }  }
   
 function displaySavedMsg(ctr,msg,shwsel) {    function displaySavedMsg(ctr,msg,shwsel) {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc = pWin.document;
     pWin.document.write("<td align=\\"center\\">"+ctr+"</td>");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>");      pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
     pWin.document.write("<td><input name=\\"msg"+ctr+"\\" type=\\"text\\" value=\\""+msg+" \\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");      pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
       pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");
 }  }
   
   function newMsg(newmsg,shwsel) {    function newMsg(newmsg,shwsel) {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc = pWin.document;
     pWin.document.write("<td align=\\"center\\">New</td>");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>");      pDoc.write("<td align=\\"center\\">New<\\/td>");
     pWin.document.write("<td><input name=\\"newmsg\\" type=\\"text\\" value=\\""+newmsg+" \\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");      pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
       pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");
 }  }
   
   function msgTail() {    function msgTail() {
     pWin.document.write("</table>");      pDoc = pWin.document;
     pWin.document.write("</td></tr></table>&nbsp;");      pDoc.write("<\\/table>");
     pWin.document.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");      pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
     pWin.document.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");      pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
     pWin.document.write("</form>");      pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
     pWin.document.write("</body></html>");      pDoc.write("<\\/form>");
       pDoc.write('$end_page_msg_central');
       pDoc.close();
 }  }
   
 //====================== Script for keyword highlight options ==============  //====================== Script for keyword highlight options ==============
Line 1245  function displaySavedMsg(ctr,msg,shwsel) Line 1597  function displaySavedMsg(ctr,msg,shwsel)
     return;      return;
   }    }
   
   
   function highlightCentral() {    function highlightCentral() {
     hwdWin = window.open('', 'KeywordHighlightCentral', 'toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx=100,screeny=75');  //    if (window.hwdWin) window.hwdWin.close();
     hwdWin.document.write("<html><head>");      var xpos = (screen.width-400)/2;
     hwdWin.document.write("<title>Highlight Central</title>");      xpos = (xpos < 0) ? '0' : xpos;
       var ypos = (screen.height-330)/2-30;
     hwdWin.document.write("<script language=javascript>");      ypos = (ypos < 0) ? '0' : ypos;
     hwdWin.document.write("function updateChoice() {");  
     hwdWin.document.write("  opener.document.SCORE.kwclr.value = radioSelection(document.hlCenter.kwdclr);");      hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
     hwdWin.document.write("  opener.document.SCORE.kwsize.value = radioSelection(document.hlCenter.kwdsize);");      hwdWin.focus();
     hwdWin.document.write("  opener.document.SCORE.kwstyle.value = radioSelection(document.hlCenter.kwdstyle);");      var hDoc = hwdWin.document;
     hwdWin.document.write("  self.close()");      hDoc.$docopen;
     hwdWin.document.write("}");      hDoc.write('$start_page_highlight_central');
       hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
     hwdWin.document.write("function radioSelection(radioButton) {");      hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options<\\/span><\\/h3><br /><br />");
     hwdWin.document.write("    var selection=null;");  
     hwdWin.document.write("    for (var i=0; i<radioButton.length; i++) {");      hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
     hwdWin.document.write("        if (radioButton[i].checked) {");      hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
     hwdWin.document.write("            selection=radioButton[i].value;");      hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>");
     hwdWin.document.write("            return selection;");  
     hwdWin.document.write("        }");  
     hwdWin.document.write("    }");  
     hwdWin.document.write("}");  
   
     hwdWin.document.write("<");  
     hwdWin.document.write("/script>");  
   
     hwdWin.document.write("</head><body bgcolor=white>");  
   
     hwdWin.document.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");  
     hwdWin.document.write("<font color=\\"green\\" size=+1>&nbsp;Keyword Highlight Options</font><br><br>");  
   
     hwdWin.document.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");  
     hwdWin.document.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");  
     hwdWin.document.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>");  
   }    }
   
   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) {     function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
     hwdWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      var hDoc = hwdWin.document;
     hwdWin.document.write("<td align=\\"left\\">");      hDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     hwdWin.document.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"</td>");      hDoc.write("<td align=\\"left\\">");
     hwdWin.document.write("<td align=\\"left\\">");      hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"<\\/td>");
     hwdWin.document.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"</td>");      hDoc.write("<td align=\\"left\\">");
     hwdWin.document.write("<td align=\\"left\\">");      hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"<\\/td>");
     hwdWin.document.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"</td>");      hDoc.write("<td align=\\"left\\">");
     hwdWin.document.write("</tr>");      hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"<\\/td>");
       hDoc.write("<\\/tr>");
   }    }
   
   function highlightend() {     function highlightend() { 
     hwdWin.document.write("</table>");      var hDoc = hwdWin.document;
     hwdWin.document.write("</td></tr></table>&nbsp;");      hDoc.write("<\\/table>");
     hwdWin.document.write("<input type=\\"button\\" value=\\"Set Options\\" onClick=\\"javascript:updateChoice()\\">&nbsp;&nbsp;");      hDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
     hwdWin.document.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");      hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");
     hwdWin.document.write("</form>");      hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
     hwdWin.document.write("</body></html>");      hDoc.write("<\\/form>");
       hDoc.write('$end_page_highlight_central');
       hDoc.close();
   }    }
   
 </script>  </script>
 SUBJAVASCRIPT  SUBJAVASCRIPT
 }  }
   
   sub get_increment {
       my $increment = $env{'form.increment'};
       if ($increment != 1 && $increment != .5 && $increment != .25 &&
           $increment != .1) {
           $increment = 1;
       }
       return $increment;
   }
   
   #--- displays the grading box, used in essay type problem and grading by page/sequence
   sub gradeBox {
       my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
       my $checkIcon = '<img alt="'.&mt('Check Mark').
    '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
       my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
       my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)') 
                              : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
       $wgt       = ($wgt > 0 ? $wgt : '1');
       my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
     '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
       my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
       my $display_part= &get_display_part($partid,$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>';
       my $ctr = 0;
       my $thisweight = 0;
       my $increment = &get_increment();
   
       my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
       while ($thisweight<=$wgt) {
    $radio.= '<td><span style="white-space: nowrap;"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
       'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
       $thisweight.')" value="'.$thisweight.'" '.
       ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
    $radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
           $thisweight += $increment;
    $ctr++;
       }
       $radio.='</tr></table>';
   
       my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
    ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
    'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
    $wgt.')" /></td>'."\n";
       $line.='<td>/'.$wgt.' '.$wgtmsg.
    ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
    ' </td><td>'."\n";
       $line.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
    'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
       if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
    $line.='<option></option>'.
       '<option value="excused" selected="selected">'.&mt('excused').'</option>';
       } else {
    $line.='<option selected="selected"></option>'.
       '<option value="excused" >'.&mt('excused').'</option>';
       }
       $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
   
   
       $result .= 
    &mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line);
   
       
       $result.='</tr></table>'."\n";
       $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".
           '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
           $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
           '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
           $aggtries.'" />'."\n";
       $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);
       return $result;
   }
   
   sub handback_box {
       my ($symb,$uname,$udom,$counter,$partid,$record) = @_;
       my ($partlist,$handgrade,$responseType) = &response_type($symb);
       my (@respids);
        my @part_response_id = &flatten_responseType($responseType);
       foreach my $part_response_id (@part_response_id) {
       my ($part,$resp) = @{ $part_response_id };
           if ($part eq $partid) {
               push(@respids,$resp);
           }
       }
       my $result;
       foreach my $respid (@respids) {
    my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
    my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
    next if (!@$files);
    my $file_counter = 1;
    foreach my $file (@$files) {
       if ($file =~ /\/portfolio\//) {
              my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
              my ($name,$version,$ext) = &file_name_version_ext($file_disp);
              $file_disp = "$name.$ext";
              $file = $file_path.$file_disp;
              $result.=&mt('Return commented version of [_1] to student.',
       '<span class="LC_filename">'.$file_disp.'</span>');
              $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
              $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />';
              $result.='('.&mt('File will be uploaded when you click on Save &amp; Next below.').')<br />';
              $file_counter++;
       }
    }
       }
       return $result;    
   }
   
   sub show_problem {
       my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode,$form) = @_;
       my $rendered;
       my %form = ((ref($form) eq 'HASH')? %{$form} : ());
       &Apache::lonxml::remember_problem_counter();
       if ($mode eq 'both' or $mode eq 'text') {
    $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
          $env{'request.course.id'},
          undef,\%form);
       }
       if ($removeform) {
    $rendered=~s|<form(.*?)>||g;
    $rendered=~s|</form>||g;
    $rendered=~s|(<input[^>]*name\s*=\s*"?)(\w+)("?)|$1would_have_been_$2$3|g;
       }
       my $companswer;
       if ($mode eq 'both' or $mode eq 'answer') {
    &Apache::lonxml::restore_problem_counter();
    $companswer=
       &Apache::loncommon::get_student_answers($symb,$uname,$udom,
       $env{'request.course.id'},
       %form);
       }
       if ($removeform) {
    $companswer=~s|<form(.*?)>||g;
    $companswer=~s|</form>||g;
    $companswer=~s|name="submit"|name="would_have_been_submit"|g;
       }
       $rendered=
    '<div class="LC_grade_show_problem_header">'.
    &mt('View of the problem').
    '</div><div class="LC_grade_show_problem_problem">'.
    $rendered.
    '</div>';
       $companswer=
    '<div class="LC_grade_show_problem_header">'.
    &mt('Correct answer').
    '</div><div class="LC_grade_show_problem_problem">'.
    $companswer.
    '</div>';
       my $result;
       if ($mode eq 'both') {
    $result=$rendered.$companswer;
       } elsif ($mode eq 'text') {
    $result=$rendered;
       } elsif ($mode eq 'answer') {
    $result=$companswer;
       }
       $result='<div class="LC_grade_show_problem">'.$result.'</div>';
       return $result;
   }
   
   sub files_exist {
       my ($r, $symb) = @_;
       my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
   
       foreach my $student (@students) {
           my ($uname,$udom,$fullname) = split(/:/,$student);
           my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
         $udom,$uname);
           my ($string,$timestamp)= &get_last_submission(\%record);
           foreach my $submission (@$string) {
               my ($partid,$respid) =
    ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
               my $files=&get_submitted_files($udom,$uname,$partid,$respid,
      \%record);
               return 1 if (@$files);
           }
       }
       return 0;
   }
   
   sub download_all_link {
       my ($r,$symb) = @_;
       my $all_students = 
    join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
   
       my $parts =
    join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
   
       my $identifier = &Apache::loncommon::get_cgi_id();
       &Apache::lonnet::appenv('cgi.'.$identifier.'.students' => $all_students,
                               'cgi.'.$identifier.'.symb' => $symb,
                               'cgi.'.$identifier.'.parts' => $parts,);
       $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
         &mt('Download All Submitted Documents').'</a>');
       return
   }
   
   sub build_section_inputs {
       my $section_inputs;
       if ($env{'form.section'} eq '') {
           $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
       } else {
           my @sections = &Apache::loncommon::get_env_multiple('form.section');
           foreach my $section (@sections) {
               $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
           }
       }
       return $section_inputs;
   }
   
   # --------------------------- show submissions of a student, option to grade 
   sub submission {
       my ($request,$counter,$total) = @_;
       my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
       $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
       my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
       $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
       my $symb = &get_symb($request); 
       if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
   
       if (!&canview($usec)) {
    $request->print('<span class="LC_warning">Unable to view requested student.('.
    $uname.':'.$udom.' in section '.$usec.' in course id '.
    $env{'request.course.id'}.')</span>');
    $request->print(&show_grading_menu_form($symb));
    return;
       }
   
       if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
       if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
       if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
       my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
       my $checkIcon = '<img alt="'.&mt('Check Mark').
    '" src="'.$request->dir_config('lonIconsURL').
    '/check.gif" height="16" border="0" />';
   
       my %old_essays;
       # header info
       if ($counter == 0) {
    &sub_page_js($request);
    &sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes');
    $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
       &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
    if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {
       &download_all_link($request, $symb);
    }
    $request->print('<h3>&nbsp;<span class="LC_info">'.&mt('Submission Record').'</span></h3>'."\n".
    '<h4>&nbsp;'.&mt('<b>Resource: </b> [_1]',$env{'form.probTitle'}).'</h4>'."\n");
   
    # option to display problem, only once else it cause problems 
           # with the form later since the problem has a form.
    if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
       my $mode;
       if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
    $mode='both';
       } elsif ($env{'form.vProb'} eq 'yes') {
    $mode='text';
       } elsif ($env{'form.vAns'} eq 'yes') {
    $mode='answer';
       }
       &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 = ();
    if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
       %keyhash = &Apache::lonnet::dump('nohist_handgrade',
        $env{'course.'.$env{'request.course.id'}.'.domain'},
        $env{'course.'.$env{'request.course.id'}.'.num'});
   
       my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
       $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
       $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
       $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
       $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
       $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
    $keyhash{$symb.'_subject'} : $env{'form.probTitle'};
       $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="'.$stu_status.'" />'."\n".
    '<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
    '<input type="hidden" name="probTitle"  value="'.$env{'form.probTitle'}.'" />'."\n".
    '<input type="hidden" name="refresh"    value="off" />'."\n".
    '<input type="hidden" name="studentNo"  value="" />'."\n".
    '<input type="hidden" name="gradeOpt"   value="" />'."\n".
    '<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".
    '<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
    '<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
    '<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
    &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"'.
    ' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
    if ($env{'form.handgrade'} eq 'yes') {
       $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
       '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
       '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
       '<input type="hidden" name="kwstyle"  value="'.$env{'form.kwstyle'}.'" />'."\n".
       '<input type="hidden" name="msgsub"   value="'.$env{'form.msgsub'}.'" />'."\n".
       '<input type="hidden" name="shownSub" value="0" />'."\n".
       '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
       foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
    $request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n");
       }
    }
   
    my ($cts,$prnmsg) = (1,'');
    while ($cts <= $env{'form.savemsgN'}) {
       $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
    (!exists($keyhash{$symb.'_savemsg'.$cts}) ? 
    &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
    &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
    '" />'."\n".
    '<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
       $cts++;
    }
    $request->print($prnmsg);
   
    if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {
   #
   # Print out the keyword options line
   #
       $request->print(<<KEYWORDS);
   &nbsp;<b>Keyword Options:</b>&nbsp;
   <a href="javascript:keywords(document.SCORE);" target="_self">List</a>&nbsp; &nbsp;
   <a href="#" onMouseDown="javascript:getSel(); return false"
    CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
   <a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br />
   KEYWORDS
   #
   # Load the other essays for similarity check
   #
               my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
       my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
       $apath=&escape($apath);
       $apath=~s/\W/\_/gs;
       %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
           }
       }
   
   # This is where output for one specific student would start
       my $add_class = ($counter%2) ? 'LC_grade_show_user_odd_row' : '';
       $request->print("\n\n".
                       '<div class="LC_grade_show_user '.$add_class.'">'.
       '<div class="LC_grade_user_name">'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</div>'.
       '<div class="LC_grade_show_user_body">'."\n");
   
       if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
    my $mode;
    if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
       $mode='both';
    } elsif ($env{'form.vProb'} eq 'all' ) {
       $mode='text';
    } elsif ($env{'form.vAns'} eq 'all') {
       $mode='answer';
    }
    &Apache::lonxml::clear_problem_counter();
    $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,{'request.prefix' => 'ctr'.$counter}));
       }
   
       my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
       my ($partlist,$handgrade,$responseType) = &response_type($symb);
   
       # Display student info
       $request->print(($counter == 0 ? '' : '<br />'));
       my $result='<div class="LC_grade_submissions">';
       
       $result.='<div class="LC_grade_submissions_header">';
       $result.= &mt('Submissions');
       $result.='<input type="hidden" name="name'.$counter.
    '" value="'.$env{'form.fullname'}.'" />'."\n";
       if ($env{'form.handgrade'} eq 'no') {
    $result.='<span class="LC_grade_check_note">'.
       &mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)."</span>\n";
   
       }
   
   
   
       # If any part of the problem is an essay-response (handgraded), then check for collaborators
       my $fullname;
       my $col_fullnames = [];
       if ($env{'form.handgrade'} eq 'yes') {
    (my $sub_result,$fullname,$col_fullnames)=
       &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
    $counter);
    $result.=$sub_result;
       }
       $request->print($result."\n");
       $request->print('</div>'."\n");
       # print student answer/submission
       # Options are (1) Handgaded submission only
       #             (2) Last submission, includes submission that is not handgraded 
       #                  (for multi-response type part)
       #             (3) Last submission plus the parts info
       #             (4) The whole record for this student
       if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
    my ($string,$timestamp)= &get_last_submission(\%record);
   
    my $lastsubonly;
   
    if ($$timestamp eq '') {
       $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
    } else {
       $lastsubonly = '<div class="LC_grade_submissions_body"> <b>Date Submitted:</b> '.$$timestamp."\n";
   
       my %seenparts;
       my @part_response_id = &flatten_responseType($responseType);
       foreach my $part (@part_response_id) {
    next if ($env{'form.lastSub'} eq 'hdgrade' 
    && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
   
    my ($partid,$respid) = @{ $part };
    my $display_part=&get_display_part($partid,$symb);
    if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
       if (exists($seenparts{$partid})) { next; }
       $seenparts{$partid}=1;
       my $submitby='<b>Part:</b> '.$display_part.
    ' <b>Collaborative submission by:</b> '.
    '<a href="javascript:viewSubmitter(\''.
    $env{"form.$uname:$udom:$partid:submitted_by"}.
    '\');" target="_self">'.
    $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
       $request->print($submitby);
       next;
    }
    my $responsetype = $responseType->{$partid}->{$respid};
    if (!exists($record{"resource.$partid.$respid.submission"})) {
       $lastsubonly.="\n".'<div class="LC_grade_submission_part"><b>Part:</b> '.
    $display_part.' <span class="LC_internal_info">( ID '.$respid.
    ' )</span>&nbsp; &nbsp;'.
    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br /><br /></div>';
       next;
    }
    foreach my $submission (@$string) {
       my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
       if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
       my ($ressub,$subval) = split(/:/,$submission,2);
       # Similarity check
       my $similar='';
       if($env{'form.checkPlag'}){
    my ($oname,$odom,$ocrsid,$oessay,$osim)=
       &most_similar($uname,$udom,$subval,\%old_essays);
    if ($osim) {
       $osim=int($osim*100.0);
       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 />';
    }
       }
       my $order=&get_order($partid,$respid,$symb,$uname,$udom);
       if ($env{'form.lastSub'} eq 'lastonly' || 
    ($env{'form.lastSub'} eq 'hdgrade' && 
    $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
    my $display_part=&get_display_part($partid,$symb);
    $lastsubonly.='<div class="LC_grade_submission_part"><b>Part:</b> '.
       $display_part.' <span class="LC_internal_info">( ID '.$respid.
       ' )</span>&nbsp; &nbsp;';
    my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
    if (@$files) {
       $lastsubonly.='<br /><span class="LC_warning">'.&mt('Like all files provided by users, this file may contain virusses').'</span><br />';
       my $file_counter = 0;
       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 />';
    }
    $lastsubonly.='<b>'.&mt('Submitted Answer:').' </b>'.
       &cleanRecord($subval,$responsetype,$symb,$partid,
    $respid,\%record,$order);
    if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
    $lastsubonly.='</div>';
       }
    }
       }
       $lastsubonly.='</div>'."\n";
    }
    $request->print($lastsubonly);
      } elsif ($env{'form.lastSub'} eq 'datesub') {
    my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);
    $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
       } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
    $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
    $env{'request.course.id'},
    $last,'.submission',
    'Apache::grades::keywords_highlight'));
       }
   
       $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
    .$udom.'" />'."\n");
       # return if view submission with no grading option
       if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
    my $toGrade.='<input type="button" value="Grade Student" '.
       'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
       .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
    $toGrade.='</div>'."\n";
    if (($env{'form.command'} eq 'submission') || 
       ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
       $toGrade.='</form>'.&show_grading_menu_form($symb); 
    }
    $request->print($toGrade);
    return;
       } else {
    $request->print('</div>'."\n");
       }
   
       # essay grading message center
       if ($env{'form.handgrade'} eq 'yes') {
    my $result='<div class="LC_grade_message_center">';
       
    $result.='<div class="LC_grade_message_center_header">'.
       &mt('Send Message').'</div><div class="LC_grade_message_center_body">';
    my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
    my $msgfor = $givenn.' '.$lastname;
    if (scalar(@$col_fullnames) > 0) {
       my $lastone = pop(@$col_fullnames);
       $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
    }
    $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
    $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
       '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
    $result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
       ',\''.$msgfor.'\');" target="_self">'.
       &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').'</a><label> ('.
       &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
       '<img src="'.$request->dir_config('lonIconsURL').
       '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
       '<br />&nbsp;('.
       &mt('Message will be sent when you click on Save &amp; Next below.').")\n";
    $result.='</div></div>';
    $request->print($result);
       }
   
       my %seen = ();
       my @partlist;
       my @gradePartRespid;
       my @part_response_id = &flatten_responseType($responseType);
       $request->print('<div class="LC_grade_assign">'.
       
       '<div class="LC_grade_assign_header">'.
       &mt('Assign Grades').'</div>'.
       '<div class="LC_grade_assign_body">');
       foreach my $part_response_id (@part_response_id) {
       my ($partid,$respid) = @{ $part_response_id };
    my $part_resp = join('_',@{ $part_response_id });
    next if ($seen{$partid} > 0);
    $seen{$partid}++;
    next if ($$handgrade{$part_resp} ne 'yes' 
    && $env{'form.lastSub'} eq 'hdgrade');
    push @partlist,$partid;
    push @gradePartRespid,$partid.'.'.$respid;
    $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
       }
       $request->print('</div></div>');
   
       $request->print('<div class="LC_grade_info_links">');
       if ($perm{'vgr'}) {
    $request->print(
       &Apache::loncommon::track_student_link(&mt('View recent activity'),
      $uname,$udom,'check'));
       }
       if ($perm{'opa'}) {
    $request->print(
       &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),
    $uname,$udom,$symb,'check'));
       }
       $request->print('</div>');
   
       $result='<input type="hidden" name="partlist'.$counter.
    '" value="'.(join ":",@partlist).'" />'."\n";
       $result.='<input type="hidden" name="gradePartRespid'.
    '" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0);
       my $ctr = 0;
       while ($ctr < scalar(@partlist)) {
    $result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.
       $partlist[$ctr].'" />'."\n";
    $ctr++;
       }
       $request->print($result.''."\n");
   
   # Done with printing info for one student
   
       $request->print('</div>');#LC_grade_show_user_body
       $request->print('</div>');#LC_grade_show_user
   
   
       # print end of form
       if ($counter == $total) {
    my $endform='<table border="0"><tr><td>'."\n";
    $endform.='<input type="button" value="'.&mt('Save &amp; Next').'" '.
       'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
       $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
    my $ntstu ='<select name="NTSTU">'.
       '<option>1</option><option>2</option>'.
       '<option>3</option><option>5</option>'.
       '<option>7</option><option>10</option></select>'."\n";
    my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
    $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
    $endform.=&mt('[_1]student(s)',$ntstu);
    $endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('Previous').'" '.
       'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
       '<input type="button" value="'.&mt('Next').'" '.
       'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
    $endform.=&mt('(Next and Previous (student) do not save the scores.)')."\n" ;
           $endform.="<input type='hidden' value='".&get_increment().
               "' name='increment' />";
    $endform.='</td></tr></table></form>';
    $endform.=&show_grading_menu_form($symb);
    $request->print($endform);
       }
       return '';
   }
   
   sub check_collaborators {
       my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
       my ($result,@col_fullnames);
       my ($classlist,undef,$fullname) = &getclasslist('all','0');
       foreach my $part (keys(%$handgrade)) {
    my $ncol = &Apache::lonnet::EXT('resource.'.$part.
    '.maxcollaborators',
    $symb,$udom,$uname);
    next if ($ncol <= 0);
    $part =~ s/\_/\./g;
    next if ($record->{'resource.'.$part.'.collaborators'} eq '');
    my (@good_collaborators, @bad_collaborators);
    foreach my $possible_collaborator
       (split(/,?\s+/,$record->{'resource.'.$part.'.collaborators'})) { 
       $possible_collaborator =~ s/[\$\^\(\)]//g;
       next if ($possible_collaborator eq '');
       my ($co_name,$co_dom) = split(/\@|:/,$possible_collaborator);
       $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
       next if ($co_name eq $uname && $co_dom eq $udom);
       # Doing this grep allows 'fuzzy' specification
       my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i, 
          keys(%$classlist));
       if (! scalar(@matches)) {
    push(@bad_collaborators, $possible_collaborator);
       } else {
    push(@good_collaborators, @matches);
       }
    }
    if (scalar(@good_collaborators) != 0) {
       $result.='<br />'.&mt('Collaborators: ');
       foreach my $name (@good_collaborators) {
    my ($lastname,$givenn) = split(/,/,$$fullname{$name});
    push(@col_fullnames, $givenn.' '.$lastname);
    $result.=$fullname->{$name}.'&nbsp; &nbsp; &nbsp;';
       }
       $result.='<br />'."\n";
       my ($part)=split(/\./,$part);
       $result.='<input type="hidden" name="collaborator'.$counter.
    '" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
    "\n";
    }
    if (scalar(@bad_collaborators) > 0) {
       $result.='<div class="LC_warning">';
       $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
       $result .= '</div>';
    }         
    if (scalar(@bad_collaborators > $ncol)) {
       $result .= '<div class="LC_warning">';
       $result .= &mt('This student has submitted too many '.
    'collaborators.  Maximum is [_1].',$ncol);
       $result .= '</div>';
    }
       }
       return ($result,$fullname,\@col_fullnames);
   }
   
   #--- Retrieve the last submission for all the parts
   sub get_last_submission {
       my ($returnhash)=@_;
       my (@string,$timestamp);
       if ($$returnhash{'version'}) {
    my %lasthash=();
    my ($version);
    for ($version=1;$version<=$$returnhash{'version'};$version++) {
       foreach my $key (sort(split(/\:/,
    $$returnhash{$version.':keys'}))) {
    $lasthash{$key}=$$returnhash{$version.':'.$key};
    $timestamp = 
       scalar(localtime($$returnhash{$version.':timestamp'}));
       }
    }
    foreach my $key (keys(%lasthash)) {
       next if ($key !~ /\.submission$/);
   
       my ($partid,$foo) = split(/submission$/,$key);
       my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
    '<span class="LC_warning">Draft Copy</span> ' : '';
       push(@string, join(':', $key, $draft.$lasthash{$key}));
    }
       }
       if (!@string) {
    $string[0] =
       '<span class="LC_warning">Nothing submitted - no attempts.</span>';
       }
       return (\@string,\$timestamp);
   }
   
   #--- High light keywords, with style choosen by user.
   sub keywords_highlight {
       my $string    = shift;
       my $size      = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
       my $styleon   = $env{'form.kwstyle'} eq ''  ? '' : $env{'form.kwstyle'};
       (my $styleoff = $styleon) =~ s/\</\<\//;
       my @keylist   = split(/[,\s+]/,$env{'form.keywords'});
       foreach my $keyword (@keylist) {
    $string =~ s/\b\Q$keyword\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$keyword$styleoff<\/font>/gi;
       }
       return $string;
   }
   
   #--- Called from submission routine
   sub processHandGrade {
       my ($request) = shift;
       my $symb   = &get_symb($request);
       my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
       my $button = $env{'form.gradeOpt'};
       my $ngrade = $env{'form.NCT'};
       my $ntstu  = $env{'form.NTSTU'};
       my $cdom   = $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum   = $env{'course.'.$env{'request.course.id'}.'.num'};
   
       if ($button eq 'Save & Next') {
    my $ctr = 0;
    while ($ctr < $ngrade) {
       my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
       my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr);
       if ($errorflag eq 'no_score') {
    $ctr++;
    next;
       }
       if ($errorflag eq 'not_allowed') {
    $request->print("<span class=\"LC_warning\">Not allowed to modify grades for $uname:$udom</span>");
    $ctr++;
    next;
       }
       my $includemsg = $env{'form.includemsg'.$ctr};
       my ($subject,$message,$msgstatus) = ('','','');
       my $restitle = &Apache::lonnet::gettitle($symb);
               my ($feedurl,$showsymb) =
    &get_feedurl_and_symb($symb,$uname,$udom);
       my $messagetail;
       if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
    $subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
    unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
    $subject.=' ['.$restitle.']';
    my (@msgnum) = split(/,/,$includemsg);
    foreach (@msgnum) {
       $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
    }
    $message =&Apache::lonfeedback::clear_out_html($message);
    if ($env{'form.withgrades'.$ctr}) {
       $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
       $messagetail = " for <a href=\"".
                      $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
    }
    $msgstatus = 
                       &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
        $message.$messagetail,
                                                        undef,$feedurl,undef,
                                                        undef,undef,$showsymb,
                                                        $restitle);
    $request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.
    $msgstatus);
       }
       if ($env{'form.collaborator'.$ctr}) {
    my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
    foreach my $collabstr (@collabstrs) {
       my ($part,@collaborators) = split(/:/,$collabstr);
       foreach my $collaborator (@collaborators) {
    my ($errorflag,$pts,$wgt) = 
       &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
      $env{'form.unamedom'.$ctr},$part);
    if ($errorflag eq 'not_allowed') {
       $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
       next;
    } elsif ($message ne '') {
       my ($baseurl,$showsymb) = 
    &get_feedurl_and_symb($symb,$collaborator,
         $udom);
       if ($env{'form.withgrades'.$ctr}) {
    $messagetail = " for <a href=\"".
                                       $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
       }
       $msgstatus = 
    &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
    }
       }
    }
       }
       $ctr++;
    }
       }
   
       if ($env{'form.handgrade'} eq 'yes') {
    # Keywords sorted in alphabatical order
    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
    my %keyhash = ();
    $env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
    $env{'form.keywords'}           =~ s/^\s+|\s+$//;
    my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
    $env{'form.keywords'} = join(' ',@keywords);
    $keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
    $keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
    $keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
    $keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
    $keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
   
    # message center - Order of message gets changed. Blank line is eliminated.
    # New messages are saved in env for the next student.
    # All messages are saved in nohist_handgrade.db
    my ($ctr,$idx) = (1,1);
    while ($ctr <= $env{'form.savemsgN'}) {
       if ($env{'form.savemsg'.$ctr} ne '') {
    $keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
    $idx++;
       }
       $ctr++;
    }
    $ctr = 0;
    while ($ctr < $ngrade) {
       if ($env{'form.newmsg'.$ctr} ne '') {
    $keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
    $env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
    $idx++;
       }
       $ctr++;
    }
    $env{'form.savemsgN'} = --$idx;
    $keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
    my $putresult = &Apache::lonnet::put
       ('nohist_handgrade',\%keyhash,$cdom,$cnum);
       }
       # Called by Save & Refresh from Highlight Attribute Window
       my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
       if ($env{'form.refresh'} eq 'on') {
    my ($ctr,$total) = (0,0);
    while ($ctr < $ngrade) {
       $total++ if  $env{'form.unamedom'.$ctr} ne '';
       $ctr++;
    }
    $env{'form.NTSTU'}=$ngrade;
    $ctr = 0;
    while ($ctr < $total) {
       my $processUser = $env{'form.unamedom'.$ctr};
       ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
       $env{'form.fullname'} = $$fullname{$processUser};
       &submission($request,$ctr,$total-1);
       $ctr++;
    }
    return '';
       }
   
   # Go directly to grade student - from submission or link from chart page
       if ($button eq 'Grade Student') {
    (undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($symb);
    my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};
    ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
    $env{'form.fullname'} = $$fullname{$processUser};
    &submission($request,0,0);
    return '';
       }
   
       # Get the next/previous one or group of students
       my $firststu = $env{'form.unamedom0'};
       my $laststu = $env{'form.unamedom'.($ngrade-1)};
       my $ctr = 2;
       while ($laststu eq '') {
    $laststu  = $env{'form.unamedom'.($ngrade-$ctr)};
    $ctr++;
    $laststu = $firststu if ($ctr > $ngrade);
       }
   
       my (@parsedlist,@nextlist);
       my ($nextflg) = 0;
       foreach (sort 
        {
    if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
        return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
    }
    return $a cmp $b;
        } (keys(%$fullname))) {
    if ($nextflg == 1 && $button =~ /Next$/) {
       push @parsedlist,$_;
    }
    $nextflg = 1 if ($_ eq $laststu);
    if ($button eq 'Previous') {
       last if ($_ eq $firststu);
       push @parsedlist,$_;
    }
       }
       $ctr = 0;
       @parsedlist = reverse @parsedlist if ($button eq 'Previous');
       my ($partlist) = &response_type($symb);
       foreach my $student (@parsedlist) {
    my $submitonly=$env{'form.submitonly'};
    my ($uname,$udom) = split(/:/,$student);
   
    if ($submitonly eq 'queued') {
       my %queue_status = 
    &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
    $udom,$uname);
       next if (!defined($queue_status{'gradingqueue'}));
    }
   
    if ($submitonly =~ /^(yes|graded|incorrect)$/) {
   #    my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
       my %status=&student_gradeStatus($symb,$udom,$uname,$partlist);
       my $submitted = 0;
       my $ungraded = 0;
       my $incorrect = 0;
       foreach (keys(%status)) {
    $submitted = 1 if ($status{$_} ne 'nothing');
    $ungraded = 1 if ($status{$_} =~ /^ungraded/);
    $incorrect = 1 if ($status{$_} =~ /^incorrect/);
    my ($foo,$partid,$foo1) = split(/\./,$_);
    if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
       $submitted = 0;
    }
       }
       next if (!$submitted && ($submitonly eq 'yes' ||
        $submitonly eq 'incorrect' ||
        $submitonly eq 'graded'));
       next if (!$ungraded && ($submitonly eq 'graded'));
       next if (!$incorrect && $submitonly eq 'incorrect');
    }
    push @nextlist,$student if ($ctr < $ntstu);
    last if ($ctr == $ntstu);
    $ctr++;
       }
   
       $ctr = 0;
       my $total = scalar(@nextlist)-1;
   
       foreach (sort @nextlist) {
    my ($uname,$udom,$submitter) = split(/:/);
    $env{'form.student'}  = $uname;
    $env{'form.userdom'}  = $udom;
    $env{'form.fullname'} = $$fullname{$_};
    &submission($request,$ctr,$total);
    $ctr++;
       }
       if ($total < 0) {
    my $the_end = '<h3><span class="LC_info">'.&mt('LON-CAPA User Message').'</span></h3><br />'."\n";
    $the_end.=&mt('<b>Message: </b> No more students for this section or class.').'<br /><br />'."\n";
    $the_end.=&mt('Click on the button below to return to the grading menu.').'<br /><br />'."\n";
    $the_end.=&show_grading_menu_form($symb);
    $request->print($the_end);
       }
       return '';
   }
   
   #---- Save the score and award for each student, if changed
   sub saveHandGrade {
       my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
       my @version_parts;
       my $usec = &Apache::lonnet::getsection($domain,$stuname,
      $env{'request.course.id'});
       if (!&canmodify($usec)) { return('not_allowed'); }
       my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
       my @parts_graded;
       my %newrecord  = ();
       my ($pts,$wgt) = ('','');
       my %aggregate = ();
       my $aggregateflag = 0;
       my @parts = split(/:/,$env{'form.partlist'.$newflg});
       foreach my $new_part (@parts) {
    #collaborator ($submi may vary for different parts
    if ($submitter && $new_part ne $part) { next; }
    my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
    if ($dropMenu eq 'excused') {
       if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
    $newrecord{'resource.'.$new_part.'.solved'} = 'excused';
    if (exists($record{'resource.'.$new_part.'.awarded'})) {
       $newrecord{'resource.'.$new_part.'.awarded'} = '';
    }
           $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
       }
    } elsif ($dropMenu eq 'reset status'
    && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
       foreach my $key (keys (%record)) {
    if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
       }
       $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_aggs($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} : 
       $env{'form.RADVAL'.$newflg.'_'.$new_part});
       if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
    next;
       }
       $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 
    $env{'form.WGT'.$newflg.'_'.$new_part};
       my $partial= $pts/$wgt;
       if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
    #do not update score for part if not changed.
                   &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
    next;
       } else {
           push @parts_graded, $new_part;
       }
       if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
    $newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
       }
       my $reckey = 'resource.'.$new_part.'.solved';
       if ($partial == 0) {
    if ($record{$reckey} ne 'incorrect_by_override') {
       $newrecord{$reckey} = 'incorrect_by_override';
    }
       } else {
    if ($record{$reckey} ne 'correct_by_override') {
       $newrecord{$reckey} = 'correct_by_override';
    }
       }    
       if ($submitter && 
    ($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
    $newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
       }
       $newrecord{'resource.'.$new_part.'.regrader'}=
    "$env{'user.name'}:$env{'user.domain'}";
    }
    # unless problem has been graded, set flag to version the submitted files
    unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || 
           $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
           $dropMenu eq 'reset status')
      {
       push (@version_parts,$new_part);
    }
       }
       my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
   
       if (%newrecord) {
           if (@version_parts) {
               my @changed_keys = &version_portfiles(\%record, \@parts_graded, 
                                   $env{'request.course.id'}, $symb, $domain, $stuname, \@version_parts);
       @newrecord{@changed_keys} = @record{@changed_keys};
       foreach my $new_part (@version_parts) {
    &handback_files($request,$symb,$stuname,$domain,$newflg,
    $new_part,\%newrecord);
       }
           }
    &Apache::lonnet::cstore(\%newrecord,$symb,
    $env{'request.course.id'},$domain,$stuname);
    &check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
        $cdom,$cnum,$domain,$stuname);
       }
       if ($aggregateflag) {
           &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
         $cdom,$cnum);
       }
       return ('',$pts,$wgt);
   }
   
   sub check_and_remove_from_queue {
       my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
       my @ungraded_parts;
       foreach my $part (@{$parts}) {
    if (    $record->{   'resource.'.$part.'.awarded'} eq ''
        && $record->{   'resource.'.$part.'.solved' } ne 'excused'
        && $newrecord->{'resource.'.$part.'.awarded'} eq ''
        && $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
    ) {
       push(@ungraded_parts, $part);
    }
       }
       if ( !@ungraded_parts ) {
    &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,
          $cnum,$domain,$stuname);
       }
   }
   
   sub handback_files {
       my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
       my $portfolio_root = &propath($domain,$stuname).'/userfiles/portfolio';
       my ($partlist,$handgrade,$responseType) = &response_type($symb);
   
       my @part_response_id = &flatten_responseType($responseType);
       foreach my $part_response_id (@part_response_id) {
       my ($part_id,$resp_id) = @{ $part_response_id };
    my $part_resp = join('_',@{ $part_response_id });
               if (($env{'form.'.$newflg.'_'.$part_resp.'_returndoc1'}) && ($new_part == $part_id)) {
                   # if multiple files are uploaded names will be 'returndoc2','returndoc3'
                   my $file_counter = 1;
    my $file_msg;
                   while ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter}) {
                       my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'.filename'};
                       my ($directory,$answer_file) = 
                           ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter} =~ /^(.*?)([^\/]*)$/);
                       my ($answer_name,$answer_ver,$answer_ext) =
           &file_name_version_ext($answer_file);
       my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
       my @dir_list = &Apache::lonnet::dirlist($portfolio_path,$domain,$stuname,$portfolio_root);
       my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                       # fix file name
                       my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
                       my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
                                              $newflg.'_'.$part_resp.'_returndoc'.$file_counter,
                                              $save_file_name);
                       if ($result !~ m|^/uploaded/|) {
                           $request->print('<span class="LC_error">An error occurred ('.$result.
                           ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</span><br />');
                       } else {
                           # mark the file as read only
                           my @files = ($save_file_name);
                           my @what = ($symb,$env{'request.course.id'},'handback');
                           &Apache::lonnet::mark_as_readonly($domain,$stuname,\@files,\@what);
    if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {
       $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
    }
                           $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;
    $file_msg.= "\n".'<br /><span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span><br />";
   
                       }
                       $request->print("<br />".$fname." will be the uploaded file name");
                       $request->print(" ".$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter});
                       $file_counter++;
                   }
    my $subject = "File Handed Back by Instructor ";
    my $message = "A file has been returned that was originally submitted in reponse to: <br />";
    $message .= "<strong>".&Apache::lonnet::gettitle($symb)."</strong><br />";
    $message .= ' The returned file(s) are named: '. $file_msg;
    $message .= " and can be found in your portfolio space.";
    my ($feedurl,$showsymb) = 
       &get_feedurl_and_symb($symb,$domain,$stuname);
                   my $restitle = &Apache::lonnet::gettitle($symb);
    my $msgstatus = 
                      &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject.
    ' (File Returned) ['.$restitle.']',$message,undef,
                            $feedurl,undef,undef,undef,$showsymb,$restitle);
               }
           }
       return;
   }
   
   sub get_feedurl_and_symb {
       my ($symb,$uname,$udom) = @_;
       my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
       $url = &Apache::lonnet::clutter($url);
       my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
    $symb,$udom,$uname);
       if ($encrypturl =~ /^yes$/i) {
    &Apache::lonenc::encrypted(\$url,1);
    &Apache::lonenc::encrypted(\$symb,1);
       }
       return ($url,$symb);
   }
   
   sub get_submitted_files {
       my ($udom,$uname,$partid,$respid,$record) = @_;
       my @files;
       if ($$record{"resource.$partid.$respid.portfiles"}) {
           my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
           foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
          push(@files,$file_url.$file);
           }
       }
       if ($$record{"resource.$partid.$respid.uploadedurl"}) {
           push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
       }
       return (\@files);
   }
   
   # ----------- 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, $stu_name, $v_flag) = @_;
       my $version_parts = join('|',@$v_flag);
       my @returned_keys;
       my $parts = join('|', @$parts_graded);
       my $portfolio_root = &propath($domain,$stu_name).
    '/userfiles/portfolio';
       foreach my $key (keys(%$record)) {
           my $new_portfiles;
           if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
               my @versioned_portfiles;
               my @portfiles = split(/\s*,\s*/,$$record{$key});
               foreach my $file (@portfiles) {
                   &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
                   my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
    my ($answer_name,$answer_ver,$answer_ext) =
       &file_name_version_ext($answer_file);
                   my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);
                   my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                   my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
                   if ($new_answer ne 'problem getting file') {
                       push(@versioned_portfiles, $directory.$new_answer);
                       &Apache::lonnet::mark_as_readonly($domain,$stu_name,
                           [$directory.$new_answer],
                           [$symb,$env{'request.course.id'},'graded']);
                   }
               }
               $$record{$key} = join(',',@versioned_portfiles);
               push(@returned_keys,$key);
           }
       } 
       return (@returned_keys);   
   }
   
   sub get_next_version {
       my ($answer_name, $answer_ext, $dir_list) = @_;
       my $version;
       foreach my $row (@$dir_list) {
           my ($file) = split(/\&/,$row,2);
           my ($file_name,$file_version,$file_ext) =
       &file_name_version_ext($file);
           if (($file_name eq $answer_name) && 
       ($file_ext eq $answer_ext)) {
                   # gets here if filename and extension match, regardless of version
                   if ($file_version ne '') {
                   # a versioned file is found  so save it for later
                   if ($file_version > $version) {
       $version = $file_version;
           }
               }
           }
       } 
       $version ++;
       return($version);
   }
   
   sub version_selected_portfile {
       my ($domain,$stu_name,$directory,$file_name,$version) = @_;
       my ($answer_name,$answer_ver,$answer_ext) =
           &file_name_version_ext($file_name);
       my $new_answer;
       $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
       if($env{'form.copy'} eq '-1') {
           $new_answer = 'problem getting file';
       } else {
           $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
           my $copy_result = &Apache::lonnet::finishuserfileupload(
                               $stu_name,$domain,'copy',
           '/portfolio'.$directory.$new_answer);
       }    
       return ($new_answer);
   }
   
   sub file_name_version_ext {
       my ($file)=@_;
       my @file_parts = split(/\./, $file);
       my ($name,$version,$ext);
       if (@file_parts > 1) {
    $ext=pop(@file_parts);
    if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) {
       $version=pop(@file_parts);
    }
    $name=join('.',@file_parts);
       } else {
    $name=join('.',@file_parts);
       }
       return($name,$version,$ext);
   }
   
   #--------------------------------------------------------------------------------------
   #
   #-------------------------- Next few routines handles grading by section or whole class
   #
   #--- Javascript to handle grading by section or whole class
   sub viewgrades_js {
       my ($request) = shift;
   
       $request->print(<<VIEWJAVASCRIPT);
   <script type="text/javascript" language="javascript">
      function writePoint(partid,weight,point) {
    var radioButton = document.classgrade["RADVAL_"+partid];
    var textbox = document.classgrade["TEXTVAL_"+partid];
    if (point == "textval") {
       point = document.classgrade["TEXTVAL_"+partid].value;
       if (isNaN(point) || parseFloat(point) < 0) {
    alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
    var resetbox = false;
    for (var i=0; i<radioButton.length; i++) {
       if (radioButton[i].checked) {
    textbox.value = i;
    resetbox = true;
       }
    }
    if (!resetbox) {
       textbox.value = "";
    }
    return;
       }
       if (parseFloat(point) > parseFloat(weight)) {
    var resp = confirm("You entered a value ("+parseFloat(point)+
      ") greater than the weight for the part. Accept?");
    if (resp == false) {
       textbox.value = "";
       return;
    }
       }
       for (var i=0; i<radioButton.length; i++) {
    radioButton[i].checked=false;
    if (parseFloat(point) == i) {
       radioButton[i].checked=true;
    }
       }
   
    } else {
       textbox.value = parseFloat(point);
    }
    for (i=0;i<document.classgrade.total.value;i++) {
       var user = document.classgrade["ctr"+i].value;
       user = user.replace(new RegExp(':', 'g'),"_");
       var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
       var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
       var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
       if (saveval != "correct") {
    scorename.value = point;
    if (selname[0].selected != true) {
       selname[0].selected = true;
    }
       }
    }
    document.classgrade["SELVAL_"+partid][0].selected = true;
       }
   
       function writeRadText(partid,weight) {
    var selval   = document.classgrade["SELVAL_"+partid];
    var radioButton = document.classgrade["RADVAL_"+partid];
           var override = document.classgrade["FORCE_"+partid].checked;
    var textbox = document.classgrade["TEXTVAL_"+partid];
    if (selval[1].selected || selval[2].selected) {
       for (var i=0; i<radioButton.length; i++) {
    radioButton[i].checked=false;
   
       }
       textbox.value = "";
   
       for (i=0;i<document.classgrade.total.value;i++) {
    var user = document.classgrade["ctr"+i].value;
    user = user.replace(new RegExp(':', 'g'),"_");
    var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
    var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
    var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
    if ((saveval != "correct") || override) {
       scorename.value = "";
       if (selval[1].selected) {
    selname[1].selected = true;
       } else {
    selname[2].selected = true;
    if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 
    {document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';}
       }
    }
       }
    } else {
       for (i=0;i<document.classgrade.total.value;i++) {
    var user = document.classgrade["ctr"+i].value;
    user = user.replace(new RegExp(':', 'g'),"_");
    var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
    var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
    var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
    if ((saveval != "correct") || override) {
       scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
       selname[0].selected = true;
    }
       }
    }    
       }
   
       function changeSelect(partid,user) {
    var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
    var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"];
    var point  = textbox.value;
    var weight = document.classgrade["weight_"+partid].value;
   
    if (isNaN(point) || parseFloat(point) < 0) {
       alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
       textbox.value = "";
       return;
    }
    if (parseFloat(point) > parseFloat(weight)) {
       var resp = confirm("You entered a value ("+parseFloat(point)+
          ") greater than the weight of the part. Accept?");
       if (resp == false) {
    textbox.value = "";
    return;
       }
    }
    selval[0].selected = true;
       }
   
       function changeOneScore(partid,user) {
    var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
    if (selval[1].selected || selval[2].selected) {
       document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = "";
       if (selval[2].selected) {
    document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
       }
           }
       }
   
       function resetEntry(numpart) {
    for (ctpart=0;ctpart<numpart;ctpart++) {
       var partid = document.classgrade["partid_"+ctpart].value;
       var radioButton = document.classgrade["RADVAL_"+partid];
       var textbox = document.classgrade["TEXTVAL_"+partid];
       var selval  = document.classgrade["SELVAL_"+partid];
       for (var i=0; i<radioButton.length; i++) {
    radioButton[i].checked=false;
   
       }
       textbox.value = "";
       selval[0].selected = true;
   
       for (i=0;i<document.classgrade.total.value;i++) {
    var user = document.classgrade["ctr"+i].value;
    user = user.replace(new RegExp(':', 'g'),"_");
    var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
    resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
    var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
    resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value;
    var saveselval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
    var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
    if (saveselval == "excused") {
       if (selname[1].selected == false) { selname[1].selected = true;}
    } else {
       if (selname[0].selected == false) {selname[0].selected = true};
    }
       }
    }
       }
   
   </script>
   VIEWJAVASCRIPT
   }
   
   #--- show scores for a section or whole class w/ option to change/update a score
   sub viewgrades {
       my ($request) = shift;
       &viewgrades_js($request);
   
       my ($symb) = &get_symb($request);
       #need to make sure we have the correct data for later EXT calls, 
       #thus invalidate the cache
       &Apache::lonnet::devalidatecourseresdata(
                    $env{'course.'.$env{'request.course.id'}.'.num'},
                    $env{'course.'.$env{'request.course.id'}.'.domain'});
       &Apache::lonnet::clear_EXT_cache_status();
   
       my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
       $result.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
   
       #view individual student submission form - called using Javascript viewOneStudent
       $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".
    &build_section_inputs().
    '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\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';
       } elsif ($env{'form.section'} eq 'none') {
    $sectionClass='Students in no Section';
       } else {
    $sectionClass='Students in Section(s) [_1]';
       }
       $result.=
    '<h3>'.
    &mt("Assign Common Grade To $sectionClass",$section_display).'</h3>';
       $result.= &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);
       my %weight = ();
       my $ctsparts = 0;
       my %seen = ();
       my @part_response_id = &flatten_responseType($responseType);
       foreach my $part_response_id (@part_response_id) {
       my ($partid,$respid) = @{ $part_response_id };
    my $part_resp = join('_',@{ $part_response_id });
    next if $seen{$partid};
    $seen{$partid}++;
    my $handgrade=$$handgrade{$part_resp};
    my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
    $weight{$partid} = $wgt eq '' ? '1' : $wgt;
   
    my $display_part=&get_display_part($partid,$symb);
    my $radio.='<table border="0"><tr>';  
    my $ctr = 0;
    while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
       $radio.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.
    'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
    ','.$ctr.')" />'.$ctr."</label></td>\n";
       $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
       $ctr++;
    }
    $radio.='</tr></table>';
    my $line = '<input type="text" name="TEXTVAL_'.
       $partid.'" size="4" '.'onChange="javascript:writePoint(\''.
    $partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
       $weight{$partid}.' (problem weight)</td>'."\n";
    $line.= '<td><select name="SELVAL_'.$partid.'"'.
       'onChange="javascript:writeRadText(\''.$partid.'\','.
    $weight{$partid}.')"> '.
       '<option selected="selected"> </option>'.
       '<option value="excused">'.&mt('excused').'</option>'.
       '<option value="reset status">'.&mt('reset status').'</option>'.
       '</select></td>'.
               '<td><label><input type="checkbox" name="FORCE_'.$partid.'" />'.&mt('Override "Correct"').'</label>';
    $line.='<input type="hidden" name="partid_'.
       $ctsparts.'" value="'.$partid.'" />'."\n";
    $line.='<input type="hidden" name="weight_'.
       $partid.'" value="'.$weight{$partid}.'" />'."\n";
   
    $result.=
       &Apache::loncommon::start_data_table_row()."\n".
       &mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line).
       &Apache::loncommon::end_data_table_row()."\n";
    $ctsparts++;
       }
       $result.=&Apache::loncommon::end_data_table()."\n".
    '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
       $result.='<input type="button" value="'.&mt('Revert to Default').'" '.
    'onClick="javascript:resetEntry('.$ctsparts.');" />';
   
       #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";
       my (@parts) = sort(&getpartlist($symb));
       my (undef,undef,$url)=&Apache::lonnet::decode_symb($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,$symb);
    if ($display =~ /^Partial Credit Factor/) {
       $result.='<th>'.
    &mt('Score Part: [_1]<br /> (weight = [_2])',
       $display_part,$weight{$partid}).'</th>'."\n";
       next;
       
    } else {
       if ($display =~ /Problem Status/) {
    my $grade_status_mt = &mt('Grade Status');
    $display =~ s{Problem Status}{$grade_status_mt<br />};
       }
       my $part_mt = &mt('Part:');
       $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part};
    }
   
    $result.='<th>'.$display.'</th>'."\n";
       }
       $result.=&Apache::loncommon::end_data_table_header_row();
   
       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');
       my $ctr = 0;
       foreach (sort 
        {
    if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
        return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
    }
    return $a cmp $b;
        } (keys(%$fullname))) {
    $ctr++;
    $result.=&viewstudentgrade($symb,$env{'request.course.id'},
      $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);
       }
       $result.=&Apache::loncommon::end_data_table();
       $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
       $result.='<input type="button" value="'.&mt('Save').'" '.
    'onClick="javascript:submit();" target="_self" /></form>'."\n";
       if (scalar(%$fullname) eq 0) {
    my $colspan=3+scalar(@parts);
    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;
   }
   
   #--- call by previous routine to display each student
   sub viewstudentgrade {
       my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
       my ($uname,$udom) = split(/:/,$student);
       my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
       my %aggregates = (); 
       my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'.
    '<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.
    "\n".$ctr.'&nbsp;</td><td>&nbsp;'.
    '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
    '\');" target="_self">'.$fullname.'</a> '.
    '<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
       $student=~s/:/_/; # colon doen't work in javascript for names
       foreach my $apart (@$parts) {
    my ($part,$type) = &split_part_type($apart);
    my $score=$record{"resource.$part.$type"};
           $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 '' ? '' : &compute_points($score,$$weight{$part});
       $result.='<input type="hidden" name="'.
    'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
       $result.='<input type="text" name="'.
    'GD_'.$student.'_'.$part.'_awarded" '.
    'onChange="javascript:changeSelect(\''.$part.'\',\''.$student.
    '\')" value="'.$pts.'" size="4" /></td>'."\n";
    } elsif ($type eq 'solved') {
       my ($status,$foo)=split(/_/,$score,2);
       $status = 'nothing' if ($status eq '');
       $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
    $part.'_solved_s" value="'.$status.'" />'."\n";
       $result.='&nbsp;<select name="'.
    'GD_'.$student.'_'.$part.'_solved" '.
    'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
       $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
    : '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
       $result.='<option value="reset status">'.&mt('reset status').'</option>';
       $result.="</select>&nbsp;</td>\n";
    } else {
       $result.='<input type="hidden" name="'.
    'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
       "\n";
       $result.='<input type="text" name="'.
    'GD_'.$student.'_'.$part.'_'.$type.'" '.
    'value="'.$score.'" size="4" /></td>'."\n";
    }
       }
       $result.=&Apache::loncommon::end_data_table_row();
       return $result;
   }
   
   #--- change scores for all the students in a section/class
   #    record does not get update if unchanged
   sub editgrades {
       my ($request) = @_;
   
       my $symb=&get_symb($request);
       my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
       my $title='<h2>'.&mt('Current Grade Status').'</h2>';
       $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
       $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
   
       my $result= &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th rowspan="2" valign="middle">'.&mt('No.').'</th>'.
    '<th rowspan="2" valign="middle">'.&nameUserString('header')."</th>\n";
       my %scoreptr = (
       'correct'  =>'correct_by_override',
       'incorrect'=>'incorrect_by_override',
       'excused'  =>'excused',
       'ungraded' =>'ungraded_attempted',
       'nothing'  => '',
       );
       my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
   
       my (@partid);
       my %weight = ();
       my %columns = ();
       my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
   
       my (@parts) = sort(&getpartlist($symb));
       my $header;
       while ($ctr < $env{'form.totalparts'}) {
    my $partid = $env{'form.partid_'.$ctr};
    push @partid,$partid;
    $weight{$partid} = $env{'form.weight_'.$partid};
    $ctr++;
       }
       my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
       foreach my $partid (@partid) {
    $header .= '<th align="center">'.&mt('Old Score').'</th>'.
       '<th align="center">'.&mt('New Score').'</th>';
    $columns{$partid}=2;
    foreach my $stores (@parts) {
       my ($part,$type) = &split_part_type($stores);
       if ($part !~ m/^\Q$partid\E/) { next;}
       if ($type eq 'awarded' || $type eq 'solved') { next; }
       my $display=&Apache::lonnet::metadata($url,$stores.'.display');
       $display =~ s/\[Part: (\w)+\]//;
       $display =~ s/Number of Attempts/Tries/;
       $header .= '<th align="center">'.&mt('Old '.$display).'</th>'.
    '<th align="center">'.&mt('New '.$display).'</th>';
       $columns{$partid}+=2;
    }
       }
       foreach my $partid (@partid) {
    my $display_part=&get_display_part($partid,$symb);
    $result .= '<th colspan="'.$columns{$partid}.'" align="center">'.
       &mt('Part: [_1] (Weight = [_2])',$display_part,$weight{$partid}).
       '</th>';
   
       }
       $result .= &Apache::loncommon::end_data_table_header_row().
    &Apache::loncommon::start_data_table_header_row().
    $header.
    &Apache::loncommon::end_data_table_header_row();
       my @noupdate;
       my ($updateCtr,$noupdateCtr) = (1,1);
       for ($i=0; $i<$env{'form.total'}; $i++) {
    my $line;
    my $user = $env{'form.ctr'.$i};
    my ($uname,$udom)=split(/:/,$user);
    my %newrecord;
    my $updateflag = 0;
    $line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
    my $usec=$classlist->{"$uname:$udom"}[5];
    if (!&canmodify($usec)) {
       my $numcols=scalar(@partid)*4+2;
       push(@noupdate,
    $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">".
    &mt('Not allowed to modify student')."</span></td></tr>");
       next;
    }
           my %aggregate = ();
           my $aggregateflag = 0;
    $user=~s/:/_/; # colon doen't work in javascript for names
    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;
       my $score;
       if ($partial eq '') {
    $score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
       } elsif ($partial > 0) {
    $score = 'correct_by_override';
       } elsif ($partial == 0) {
    $score = 'incorrect_by_override';
       }
       my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
       $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
   
       $newrecord{'resource.'.$_.'.regrader'}=
    "$env{'user.name'}:$env{'user.domain'}";
       if ($dropMenu eq 'reset status' &&
    $old_score ne '') { # ignore if no previous attempts => nothing to reset
    $newrecord{'resource.'.$_.'.tries'} = '';
    $newrecord{'resource.'.$_.'.solved'} = '';
    $newrecord{'resource.'.$_.'.award'} = '';
    $newrecord{'resource.'.$_.'.awarded'} = '';
    $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 '';
    $newrecord{'resource.'.$_.'.solved'}   = $score;
    $rec_update++;
       }
   
       $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
    '<td align="center">'.$awarded.
    ($score eq 'excused' ? $score : '').'&nbsp;</td>';
   
   
       my $partid=$_;
       foreach my $stores (@parts) {
    my ($part,$type) = &split_part_type($stores);
    if ($part !~ m/^\Q$partid\E/) { next;}
    if ($type eq 'awarded' || $type eq 'solved') { next; }
    my $old_aw    = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
    my $awarded   = $env{'form.GD_'.$user.'_'.$part.'_'.$type};
    if ($awarded ne '' && $awarded ne $old_aw) {
       $newrecord{'resource.'.$part.'.'.$type}= $awarded;
       $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
       $updateflag=1;
    }
    $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
       '<td align="center">'.$awarded.'&nbsp;</td>';
       }
    }
    $line.="\n";
   
    my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
    my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
   
    if ($updateflag) {
       $count++;
       &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
       $udom,$uname);
   
       if (&Apache::bridgetask::in_queue('gradingqueue',$symb,$cdom,
         $cnum,$udom,$uname)) {
    # need to figure out if should be in queue.
    my %record =  
       &Apache::lonnet::restore($symb,$env{'request.course.id'},
        $udom,$uname);
    my $all_graded = 1;
    my $none_graded = 1;
    foreach my $part (@parts) {
       if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
    $all_graded = 0;
       } else {
    $none_graded = 0;
       }
    }
   
    if ($all_graded || $none_graded) {
       &Apache::bridgetask::remove_from_queue('gradingqueue',
      $symb,$cdom,$cnum,
      $udom,$uname);
    }
       }
   
       $result.=&Apache::loncommon::start_data_table_row().
    '<td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line.
    &Apache::loncommon::end_data_table_row();
       $updateCtr++;
    } else {
       push(@noupdate,
    '<td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line);
       $noupdateCtr++;
    }
           if ($aggregateflag) {
               &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
     $cdom,$cnum);
           }
       }
       if (@noupdate) {
   # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
    my $numcols=scalar(@partid)*4+2;
    $result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
       '<td align="center" colspan="'.$numcols.'">'.
       &mt('No Changes Occurred For the Students Below').
       '</td>'.
       &Apache::loncommon::end_data_table_row();
    foreach my $line (@noupdate) {
       $result.=
    &Apache::loncommon::start_data_table_row().
    $line.
    &Apache::loncommon::end_data_table_row();
    }
       }
       $result .= &Apache::loncommon::end_data_table().
    &show_grading_menu_form($symb);
       my $msg = '<p><b>'.
    &mt('Number of records updated = [_1] for [quant,_2,student].',
       $rec_update,$count).'</b><br />'.
    '<b>'.&mt('Total number of students = [_1]',$env{'form.total'}).
    '</b></p>';
       return $title.$msg.$result;
   }
   
   sub split_part_type {
       my ($partstr) = @_;
       my ($temp,@allparts)=split(/_/,$partstr);
       my $type=pop(@allparts);
       my $part=join('_',@allparts);
       return ($part,$type);
   }
   
   #------------- end of section for handling grading by section/class ---------
   #
   #----------------------------------------------------------------------------
   
   
   #----------------------------------------------------------------------------
   #
   #-------------------------- Next few routines handles grading by csv upload
   #
   #--- Javascript to handle csv upload
 sub csvupload_javascript_reverse_associate {  sub csvupload_javascript_reverse_associate {
       my $error1=&mt('You need to specify the username or ID');
       my $error2=&mt('You need to specify at least one grading field');
   return(<<ENDPICK);    return(<<ENDPICK);
   function verify(vf) {    function verify(vf) {
     var foundsomething=0;      var foundsomething=0;
     var founduname=0;      var founduname=0;
     var founddomain=0;      var foundID=0;
     for (i=0;i<=vf.nfields.value;i++) {      for (i=0;i<=vf.nfields.value;i++) {
       tw=eval('vf.f'+i+'.selectedIndex');        tw=eval('vf.f'+i+'.selectedIndex');
       if (i==0 && tw!=0) { founduname=1; }        if (i==0 && tw!=0) { foundID=1; }
       if (i==1 && tw!=0) { founddomain=1; }        if (i==1 && tw!=0) { founduname=1; }
       if (i!=0 && i!=1 && tw!=0) { foundsomething=1; }        if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }
     }      }
     if (founduname==0 || founddomain==0) {      if (founduname==0 && foundID==0) {
       alert('You need to specify at both the username and domain');   alert('$error1');
       return;   return;
     }      }
     if (foundsomething==0) {      if (foundsomething==0) {
       alert('You need to specify at least one grading field');   alert('$error2');
       return;   return;
     }      }
     vf.submit();      vf.submit();
   }    }
Line 1345  ENDPICK Line 3692  ENDPICK
 }  }
   
 sub csvupload_javascript_forward_associate {  sub csvupload_javascript_forward_associate {
       my $error1=&mt('You need to specify the username or ID');
       my $error2=&mt('You need to specify at least one grading field');
   return(<<ENDPICK);    return(<<ENDPICK);
   function verify(vf) {    function verify(vf) {
     var foundsomething=0;      var foundsomething=0;
     var founduname=0;      var founduname=0;
     var founddomain=0;      var foundID=0;
     for (i=0;i<=vf.nfields.value;i++) {      for (i=0;i<=vf.nfields.value;i++) {
       tw=eval('vf.f'+i+'.selectedIndex');        tw=eval('vf.f'+i+'.selectedIndex');
       if (tw==1) { founduname=1; }        if (tw==1) { foundID=1; }
       if (tw==2) { founddomain=1; }        if (tw==2) { founduname=1; }
       if (tw>2) { foundsomething=1; }        if (tw>3) { foundsomething=1; }
     }      }
     if (founduname==0 || founddomain==0) {      if (founduname==0 && foundID==0) {
       alert('You need to specify at both the username and domain');   alert('$error1');
       return;   return;
     }      }
     if (foundsomething==0) {      if (foundsomething==0) {
       alert('You need to specify at least one grading field');   alert('$error2');
       return;   return;
     }      }
     vf.submit();      vf.submit();
   }    }
Line 1380  ENDPICK Line 3729  ENDPICK
 }  }
   
 sub csvuploadmap_header {  sub csvuploadmap_header {
   my ($request,$symb,$url,$datatoken,$distotal)= @_;      my ($request,$symb,$datatoken,$distotal)= @_;
   my $result;      my $javascript;
   my $javascript;      if ($env{'form.upfile_associate'} eq 'reverse') {
   if ($ENV{'form.upfile_associate'} eq 'reverse') {   $javascript=&csvupload_javascript_reverse_associate();
     $javascript=&csvupload_javascript_reverse_associate();      } else {
   } else {   $javascript=&csvupload_javascript_forward_associate();
     $javascript=&csvupload_javascript_forward_associate();      }
   }  
   $request->print(<<ENDPICK);      my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
       my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
       my $ignore=&mt('Ignore First Line');
       $symb = &Apache::lonenc::check_encrypt($symb);
       $request->print(<<ENDPICK);
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <h3>Uploading Class Grades for resource $url</h3>  <h3><span class="LC_info">Uploading Class Grades</span></h3>
 <hr>  $result
   <hr />
 <h3>Identify fields</h3>  <h3>Identify fields</h3>
 Total number of records found in file: $distotal <hr />  Total number of records found in file: $distotal <hr />
 Enter as many fields as you can. The system will inform you and bring you back  Enter as many fields as you can. The system will inform you and bring you back
 to this page if the data selected is insufficient to run your class.<hr />  to this page if the data selected is insufficient to run your class.<hr />
 <input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />  <input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
   <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>
 <input type="hidden" name="associate"  value="" />  <input type="hidden" name="associate"  value="" />
 <input type="hidden" name="phase"      value="three" />  <input type="hidden" name="phase"      value="three" />
 <input type="hidden" name="datatoken"  value="$datatoken" />  <input type="hidden" name="datatoken"  value="$datatoken" />
 <input type="hidden" name="fileupload" value="$ENV{'form.fileupload'}" />  <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" />
 <input type="hidden" name="upfiletype" value="$ENV{'form.upfiletype'}" />  <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" />
 <input type="hidden" name="upfile_associate"   <input type="hidden" name="upfile_associate" 
                                        value="$ENV{'form.upfile_associate'}" />                                         value="$env{'form.upfile_associate'}" />
 <input type="hidden" name="symb"       value="$symb" />  <input type="hidden" name="symb"       value="$symb" />
 <input type="hidden" name="url"        value="$url" />  <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 <input type="hidden" name="command"    value="csvuploadassign" />  <input type="hidden" name="probTitle"  value="$env{'form.probTitle'}" />
   <input type="hidden" name="command"    value="csvuploadoptions" />
 <hr />  <hr />
 <script type="text/javascript" language="Javascript">  <script type="text/javascript" language="Javascript">
 $javascript  $javascript
 </script>  </script>
 ENDPICK  ENDPICK
   return '';      return '';
   
 }  }
   
 sub csvupload_fields {  sub csvupload_fields {
   my ($url) = @_;      my ($symb) = @_;
   my (@parts) = &getpartlist($url);      my (@parts) = &getpartlist($symb);
   my @fields=(['username','Student Username'],['domain','Student Domain']);      my @fields=(['ID','Student ID'],
   foreach my $part (sort(@parts)) {   ['username','Student Username'],
     my @datum;   ['domain','Student Domain']);
     my $display=&Apache::lonnet::metadata($url,$part.'.display');      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     my $name=$part;      foreach my $part (sort(@parts)) {
     if  (!$display) { $display = $name; }   my @datum;
     @datum=($name,$display);   my $display=&Apache::lonnet::metadata($url,$part.'.display');
     push(@fields,\@datum);   my $name=$part;
   }   if  (!$display) { $display = $name; }
   return (@fields);   @datum=($name,$display);
    if ($name=~/^stores_(.*)_awarded/) {
       push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
    }
    push(@fields,\@datum);
       }
       return (@fields);
 }  }
   
 sub csvuploadmap_footer {  sub csvuploadmap_footer {
   my ($request,$i,$keyfields) =@_;      my ($request,$i,$keyfields) =@_;
   $request->print(<<ENDPICK);      $request->print(<<ENDPICK);
 </table>  </table>
 <input type="hidden" name="nfields" value="$i" />  <input type="hidden" name="nfields" value="$i" />
 <input type="hidden" name="keyfields" value="$keyfields" />  <input type="hidden" name="keyfields" value="$keyfields" />
Line 1442  sub csvuploadmap_footer { Line 3804  sub csvuploadmap_footer {
 ENDPICK  ENDPICK
 }  }
   
 sub csvuploadmap {  sub checkforfile_js {
   my ($request)= @_;      my $result =<<CSVFORMJS;
   my ($symb,$url)=&get_symb_and_url($request);  <script type="text/javascript" language="javascript">
   if (!$symb) {return '';}      function checkUpload(formname) {
   my $datatoken;   if (formname.upfile.value == "") {
   if (!$ENV{'form.datatoken'}) {      alert("Please use the browse button to select a file from your local directory.");
     $datatoken=&Apache::loncommon::upfile_store($request);      return false;
   } else {   }
     $datatoken=$ENV{'form.datatoken'};   formname.submit();
     &Apache::loncommon::load_tmp_file($request);  
   }  
   my @records=&Apache::loncommon::upfile_record_sep();  
   &csvuploadmap_header($request,$symb,$url,$datatoken,$#records+1);  
   my $i;  
   my $keyfields;  
   if (@records) {  
     my @fields=&csvupload_fields($url);  
     if ($ENV{'form.upfile_associate'} eq 'reverse') {  
       &Apache::loncommon::csv_print_samples($request,\@records);  
       $i=&Apache::loncommon::csv_print_select_table($request,\@records,  
     \@fields);  
       foreach (@fields) { $keyfields.=$_->[0].','; }  
       chop($keyfields);  
     } else {  
       unshift(@fields,['none','']);  
       $i=&Apache::loncommon::csv_samples_select_table($request,\@records,  
       \@fields);  
       my %sone=&Apache::loncommon::record_sep($records[0]);  
       $keyfields=join(',',sort(keys(%sone)));  
     }      }
   }      </script>
   &csvuploadmap_footer($request,$i,$keyfields);  CSVFORMJS
   return '';      return $result;
 }  }
   
 sub csvuploadassign {  sub upcsvScores_form {
   my ($request)= @_;      my ($request) = shift;
   my ($symb,$url)=&get_symb_and_url($request);      my ($symb)=&get_symb($request);
   if (!$symb) {return '';}      if (!$symb) {return '';}
   &Apache::loncommon::load_tmp_file($request);      my $result=&checkforfile_js();
   my @gradedata=&Apache::loncommon::upfile_record_sep();      $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
   my @keyfields = split(/\,/,$ENV{'form.keyfields'});      my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
   my %fields=();      $result.=$table;
   for (my $i=0; $i<=$ENV{'form.nfields'}; $i++) {      $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
     if ($ENV{'form.upfile_associate'} eq 'reverse') {      $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
       if ($ENV{'form.f'.$i} ne 'none') {      $result.='&nbsp;<b>'.&mt('Specify a file containing the class scores for current resource').
  $fields{$keyfields[$i]}=$ENV{'form.f'.$i};   '.</b></td></tr>'."\n";
       }      $result.='<tr bgcolor=#ffffe6><td>'."\n";
       my $upload=&mt("Upload Scores");
       my $upfile_select=&Apache::loncommon::upfile_select_html();
       my $ignore=&mt('Ignore First Line');
       $symb = &Apache::lonenc::check_encrypt($symb);
       $result.=<<ENDUPFORM;
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
   <input type="hidden" name="symb" value="$symb" />
   <input type="hidden" name="command" value="csvuploadmap" />
   <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
   <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
   $upfile_select
   <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
   <label><input type="checkbox" name="noFirstLine" />$ignore</label>
   </form>
   ENDUPFORM
       $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
                              &mt("How do I create a CSV file from a spreadsheet"))
       .'</td></tr></table>'."\n";
       $result.='</td></tr></table><br /><br />'."\n";
       $result.=&show_grading_menu_form($symb);
       return $result;
   }
   
   
   sub csvuploadmap {
       my ($request)= @_;
       my ($symb)=&get_symb($request);
       if (!$symb) {return '';}
   
       my $datatoken;
       if (!$env{'form.datatoken'}) {
    $datatoken=&Apache::loncommon::upfile_store($request);
     } else {      } else {
       if ($ENV{'form.f'.$i} ne 'none') {   $datatoken=$env{'form.datatoken'};
  $fields{$ENV{'form.f'.$i}}=$keyfields[$i];   &Apache::loncommon::load_tmp_file($request);
       }  
     }      }
   }      my @records=&Apache::loncommon::upfile_record_sep();
   $request->print('<h3>Assigning Grades</h3>');      if ($env{'form.noFirstLine'}) { shift(@records); }
   my $courseid=$ENV{'request.course.id'};      &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
 #  my $cdom=$ENV{"course.$courseid.domain"};      my ($i,$keyfields);
 #  my $cnum=$ENV{"course.$courseid.num"};      if (@records) {
   my ($classlist) = &getclasslist('all','1');   my @fields=&csvupload_fields($symb);
   my @skipped;  
   my $countdone=0;   if ($env{'form.upfile_associate'} eq 'reverse') {
   foreach my $grade (@gradedata) {      &Apache::loncommon::csv_print_samples($request,\@records);
     my %entries=&Apache::loncommon::record_sep($grade);      $i=&Apache::loncommon::csv_print_select_table($request,\@records,
     my $username=$entries{$fields{'username'}};    \@fields);
     my $domain=$entries{$fields{'domain'}};      foreach (@fields) { $keyfields.=$_->[0].','; }
     if (!exists($$classlist{"$username:$domain"})) {      chop($keyfields);
       push(@skipped,"$username:$domain");   } else {
       next;      unshift(@fields,['none','']);
     }      $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
     my %grades;      \@fields);
     foreach my $dest (keys(%fields)) {              foreach my $rec (@records) {
       if ($dest eq 'username' || $dest eq 'domain') { next; }                  my %temp = &Apache::loncommon::record_sep($rec);
       if ($entries{$fields{$dest}} eq '') { next; }                  if (%temp) {
       my $store_key=$dest;                      $keyfields=join(',',sort(keys(%temp)));
       $store_key=~s/^stores/resource/;                      last;
       $store_key=~s/_/\./g;                  }
       $grades{$store_key}=$entries{$fields{$dest}};              }
     }   }
     $grades{"resource.regrader"}="$ENV{'user.name'}:$ENV{'user.domain'}";      }
     &Apache::lonnet::cstore(\%grades,$symb,$ENV{'request.course.id'},      &csvuploadmap_footer($request,$i,$keyfields);
     $domain,$username);      $request->print(&show_grading_menu_form($symb));
     $request->print('.');  
     $request->rflush();      return '';
     $countdone++;  
   }  
   $request->print("<br />Stored $countdone students\n");  
   if (@skipped) {  
     $request->print('<br /><font size="+1"><b>Skipped Students</b></font><br />');  
     foreach my $student (@skipped) { $request->print("<br />$student"); }  
   }  
   $request->print(&view_edit_entire_class_form($symb,$url));  
   $request->print(&show_grading_menu_form($symb,$url));  
   return '';  
 }  
   
 sub send_header {  
   my ($request)= @_;  
   $request->print(&Apache::lontexconvert::header());  
 #  $request->print("  
 #<script>  
 #remotewindow=open('','homeworkremote');  
 #remotewindow.close();  
 #</script>");   
   $request->print('<body bgcolor="#FFFFFF">');  
 }  
   
 sub send_footer {  
   my ($request)= @_;  
   $request->print('</body>');  
   $request->print(&Apache::lontexconvert::footer());  
 }  }
   
 sub handler {  sub csvuploadoptions {
   my $request=$_[0];      my ($request)= @_;
       my ($symb)=&get_symb($request);
       my $checked=(($env{'form.noFirstLine'})?'1':'0');
       my $ignore=&mt('Ignore First Line');
       $request->print(<<ENDPICK);
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
   <h3><span class="LC_info">Uploading Class Grade Options</span></h3>
   <input type="hidden" name="command"    value="csvuploadassign" />
   <!--
   <p>
   <label>
      <input type="checkbox" name="show_full_results" />
      Show a table of all changes
   </label>
   </p>
   -->
   <p>
   <label>
      <input type="checkbox" name="overwite_scores" checked="checked" />
      Overwrite any existing score
   </label>
   </p>
   ENDPICK
       my %fields=&get_fields();
       if (!defined($fields{'domain'})) {
    my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
    $request->print("\n<p> Users are in domain: ".$domform."</p>\n");
       }
       foreach my $key (sort(keys(%env))) {
    if ($key !~ /^form\.(.*)$/) { next; }
    my $cleankey=$1;
    if ($cleankey eq 'command') { next; }
    $request->print('<input type="hidden" name="'.$cleankey.
    '"  value="'.$env{$key}.'" />'."\n");
       }
       # FIXME do a check for any duplicated user ids...
       # FIXME do a check for any invalid user ids?...
       $request->print('<input type="submit" value="Assign Grades" /><br />
   <hr /></form>'."\n");
       $request->print(&show_grading_menu_form($symb));
       return '';
   }
   
   if ($ENV{'browser.mathml'}) {  sub get_fields {
     $request->content_type('text/xml');      my %fields;
   } else {      my @keyfields = split(/\,/,$env{'form.keyfields'});
     $request->content_type('text/html');      for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
   }   if ($env{'form.upfile_associate'} eq 'reverse') {
   $request->send_http_header;      if ($env{'form.f'.$i} ne 'none') {
   return OK if $request->header_only;   $fields{$keyfields[$i]}=$env{'form.f'.$i};
   &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});      }
   my $url=$ENV{'form.url'};   } else {
   my $symb=$ENV{'form.symb'};      if ($env{'form.f'.$i} ne 'none') {
   my $command=$ENV{'form.command'};   $fields{$env{'form.f'.$i}}=$keyfields[$i];
   if (!$url) {      }
     my ($temp1,$temp2);   }
     ($temp1,$temp2,$ENV{'form.url'})=split(/___/,$symb);      }
     $url = $ENV{'form.url'};      return %fields;
   }  }
   &send_header($request);  
   if ($url eq '' && $symb eq '') {  sub csvuploadassign {
      if ($ENV{'user.adv'}) {      my ($request)= @_;
          if (($ENV{'form.codeone'}) && ($ENV{'form.codetwo'}) &&      my ($symb)=&get_symb($request);
              ($ENV{'form.codethree'})) {      if (!$symb) {return '';}
              my $token=$ENV{'form.codeone'}.'*'.$ENV{'form.codetwo'}.'*'.      my $error_msg = '';
         $ENV{'form.codethree'};      &Apache::loncommon::load_tmp_file($request);
              my ($tsymb,$tuname,$tudom,$tcrsid)=      my @gradedata = &Apache::loncommon::upfile_record_sep();
  &Apache::lonnet::checkin($token);      if ($env{'form.noFirstLine'}) { shift(@gradedata); }
              if ($tsymb) {      my %fields=&get_fields();
                 my ($map,$id,$url)=split(/\_\_\_/,$tsymb);      $request->print('<h3>Assigning Grades</h3>');
                 if (&Apache::lonnet::allowed('mgr',$tcrsid)) {      my $courseid=$env{'request.course.id'};
                    $request->print(      my ($classlist) = &getclasslist('all',0);
                      &Apache::lonnet::ssi('/res/'.$url,      my @notallowed;
                         ('grade_username' => $tuname,      my @skipped;
                          'grade_domain' => $tudom,      my $countdone=0;
                          'grade_courseid' => $tcrsid,      foreach my $grade (@gradedata) {
                          'grade_symb' => $tsymb)));   my %entries=&Apache::loncommon::record_sep($grade);
    my $domain;
    if ($entries{$fields{'domain'}}) {
       $domain=$entries{$fields{'domain'}};
    } else {
       $domain=$env{'form.default_domain'};
    }
    $domain=~s/\s//g;
    my $username=$entries{$fields{'username'}};
    $username=~s/\s//g;
    if (!$username) {
       my $id=$entries{$fields{'ID'}};
       $id=~s/\s//g;
       my %ids=&Apache::lonnet::idget($domain,$id);
       $username=$ids{$id};
    }
    if (!exists($$classlist{"$username:$domain"})) {
       my $id=$entries{$fields{'ID'}};
       $id=~s/\s//g;
       if ($id) {
    push(@skipped,"$id:$domain");
       } else {
    push(@skipped,"$username:$domain");
       }
       next;
    }
    my $usec=$classlist->{"$username:$domain"}[5];
    if (!&canmodify($usec)) {
       push(@notallowed,"$username:$domain");
       next;
    }
    my %points;
    my %grades;
    foreach my $dest (keys(%fields)) {
       if ($dest eq 'ID' || $dest eq 'username' ||
    $dest eq 'domain') { next; }
       if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
       if ($dest=~/stores_(.*)_points/) {
    my $part=$1;
    my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
         $symb,$domain,$username);
                   if ($wgt) {
                       $entries{$fields{$dest}}=~s/\s//g;
                       my $pcr=$entries{$fields{$dest}} / $wgt;
                       my $award=($pcr == 0) ? 'incorrect_by_override'
                                             : 'correct_by_override';
                       $grades{"resource.$part.awarded"}=$pcr;
                       $grades{"resource.$part.solved"}=$award;
                       $points{$part}=1;
                 } else {                  } else {
                    $request->print('<h1>Not authorized: '.$token.'</h1>');                      $error_msg = "<br />" .
                 }                                     &mt("Some point values were assigned"
                               ." for problems with a weight "
                               ."of zero. These values were "
                               ."ignored.");
                   }
       } else {
    if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
    if ($dest=~/stores_(.*)_solved/)  { if ($points{$1}) {next;} }
    my $store_key=$dest;
    $store_key=~s/^stores/resource/;
    $store_key=~s/_/\./g;
    $grades{$store_key}=$entries{$fields{$dest}};
       }
    }
    if (! %grades) { push(@skipped,"$username:$domain no data to save"); }
    $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
    my $result=&Apache::lonnet::cstore(\%grades,$symb,
      $env{'request.course.id'},
      $domain,$username);
    if ($result eq 'ok') {
       $request->print('.');
    } else {
       $request->print("<p>
                                 <span class=\"LC_error\">
                                    Failed to save student $username:$domain.
                                    Message when trying to save was ($result)
                                 </span>
                                </p>" );
    }
    $request->rflush();
    $countdone++;
       }
       $request->print("<br />Saved $countdone students\n");
       if (@skipped) {
    $request->print('<p><h4><b>Skipped Students</b></h4></p>');
    foreach my $student (@skipped) { $request->print("$student<br />\n"); }
       }
       if (@notallowed) {
    $request->print('<p><span class="LC_error">Students Not Allowed to Modify</span></p>');
    foreach my $student (@notallowed) { $request->print("$student<br />\n"); }
       }
       $request->print("<br />\n");
       $request->print(&show_grading_menu_form($symb));
       return $error_msg;
   }
   #------------- end of section for handling csv file upload ---------
   #
   #-------------------------------------------------------------------
   #
   #-------------- Next few routines handle grading by page/sequence
   #
   #--- Select a page/sequence and a student to grade
   sub pickStudentPage {
       my ($request) = shift;
   
       $request->print(<<LISTJAVASCRIPT);
   <script type="text/javascript" language="javascript">
   
   function checkPickOne(formname) {
       if (radioSelection(formname.student) == null) {
    alert("Please select the student you wish to grade.");
    return;
       }
       ptr = pullDownSelection(formname.selectpage);
       formname.page.value = formname["page"+ptr].value;
       formname.title.value = formname["title"+ptr].value;
       formname.submit();
   }
   
   </script>
   LISTJAVASCRIPT
       &commonJSfunctions($request);
       my ($symb) = &get_symb($request);
       my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
       my $cnum      = $env{"course.$env{'request.course.id'}.num"};
       my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
   
       my $result='<h3><span class="LC_info">&nbsp;'.
    &mt('Manual Grading by Page or Sequence').'</span></h3>';
   
       $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
       my ($titles,$symbx) = &getSymbMap();
       my ($curpage) =&Apache::lonnet::decode_symb($symb); 
   #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
   #    my $type=($curpage =~ /\.(page|sequence)/);
       my $select = '<select name="selectpage">'."\n";
       my $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $select.='<option value="'.$ctr.'" '.
       ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
       '>'.$showtitle.'</option>'."\n";
    $ctr++;
       }
       $select.= '</select>';
       $result.=&mt('&nbsp;<b>Problems from:</b> [_1]',$select)."<br />\n";
   
       $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
    $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
    $ctr++;
       }
       $result.='<input type="hidden" name="page" />'."\n".
    '<input type="hidden" name="title" />'."\n";
   
       my $options =
    '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
    '<label><input type="radio" name="vProb" value="yes" /> '.&mt('yes').' </label>'."<br />\n";
       $result.='&nbsp;'.&mt('<b>View Problems Text: </b> [_1]',$options);
   
       $options =
    '<label><input type="radio" name="lastSub" value="none" /> '.&mt('none').' </label>'."\n".
    '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.&mt('by dates and submissions').'</label>'."\n".
    '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";
       $result.='&nbsp;'.&mt('<b>Submission Details: </b>[_1]',$options);
       
       $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";
   
       $result.='&nbsp;'.&mt('<b>Use CODE: [_1] </b>',
     '<input type="text" name="CODE" value="" />').
         '<br />'."\n";
   
       $result.='&nbsp;<input type="button" '.
    'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next-&gt;').'" /><br />'."\n";
   
       $request->print($result);
   
       my $studentTable.='&nbsp;<b>'.&mt('Select a student you wish to grade and then click on the Next button.').'</b><br />'.
    &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th align="right">&nbsp;'.&mt('No.').'</th>'.
    '<th>'.&nameUserString('header').'</th>'.
    '<th align="right">&nbsp;'.&mt('No.').'</th>'.
    '<th>'.&nameUserString('header').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
    
       my (undef,undef,$fullname) = &getclasslist($getsec,'1');
       my $ptr = 1;
       foreach my $student (sort 
    {
        if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
    return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
        }
        return $a cmp $b;
    } (keys(%$fullname))) {
    my ($uname,$udom) = split(/:/,$student);
    $studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
                                     : '</td>');
    $studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
    $studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
       .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
    $studentTable.=
       ($ptr%2 == 0 ? '</td>'.&Apache::loncommon::end_data_table_row() 
                            : '');
    $ptr++;
       }
       if ($ptr%2 == 0) {
    $studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td>'.
       &Apache::loncommon::end_data_table_row();
       }
       $studentTable.=&Apache::loncommon::end_data_table()."\n";
       $studentTable.='<input type="button" '.
    'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next-&gt;').'" /></form>'."\n";
   
       $studentTable.=&show_grading_menu_form($symb);
       $request->print($studentTable);
   
       return '';
   }
   
   sub getSymbMap {
       my $navmap = Apache::lonnavmaps::navmap->new();
   
       my %symbx = ();
       my @titles = ();
       my $minder = 0;
   
       # Gather every sequence that has problems.
       my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
          1,0,1);
       for my $sequence ($navmap->getById('0.0'), @sequences) {
    if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
       my $title = $minder.'.'.
    &HTML::Entities::encode($sequence->compTitle(),'"\'&');
       push(@titles, $title); # minder in case two titles are identical
       $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
       $minder++;
    }
       }
       return \@titles,\%symbx;
   }
   
   #
   #--- Displays a page/sequence w/wo problems, w/wo submissions
   sub displayPage {
       my ($request) = shift;
   
       my ($symb) = &get_symb($request);
       my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
       my $cnum      = $env{"course.$env{'request.course.id'}.num"};
       my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
       my $pageTitle = $env{'form.page'};
       my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
       my ($uname,$udom) = split(/:/,$env{'form.student'});
       my $usec=$classlist->{$env{'form.student'}}[5];
   
       #need to make sure we have the correct data for later EXT calls, 
       #thus invalidate the cache
       &Apache::lonnet::devalidatecourseresdata(
                    $env{'course.'.$env{'request.course.id'}.'.num'},
                    $env{'course.'.$env{'request.course.id'}.'.domain'});
       &Apache::lonnet::clear_EXT_cache_status();
   
       if (!&canview($usec)) {
    $request->print('<span class="LC_warning">'.&mt('Unable to view requested student. ([_1])',$env{'form.student'}).'</span>');
    $request->print(&show_grading_menu_form($symb));
    return;
       }
       my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
       $result.='<h3>&nbsp;'.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
    '</h3>'."\n";
       if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
    $result.='<h3>&nbsp;'.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
       } else {
    delete($env{'form.CODE'});
       }
       &sub_page_js($request);
       $request->print($result);
   
       my $navmap = Apache::lonnavmaps::navmap->new();
       my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
       my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
       if (!$map) {
    $request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
    $request->print(&show_grading_menu_form($symb));
    return; 
       }
       my $iterator = $navmap->getIterator($map->map_start(),
    $map->map_finish());
   
       my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
    '<input type="hidden" name="command" value="gradeByPage" />'."\n".
    '<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n".
    '<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
    '<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
    '<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
    '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="overRideScore" value="no" />'."\n".
    '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
   
       if (defined($env{'form.CODE'})) {
    $studentTable.=
       '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
       }
       my $checkIcon = '<img alt="'.&mt('Check Mark').
    '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
   
       $studentTable.='&nbsp;'.&mt('<b>Note:</b> Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon)."\n".
    &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th align="center">&nbsp;Prob.&nbsp;</th>'.
    '<th>&nbsp;'.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
   
       &Apache::lonxml::clear_problem_counter();
       my ($depth,$question,$prob) = (1,1,1);
       $iterator->next(); # skip the first BEGIN_MAP
       my $curRes = $iterator->next(); # for "current resource"
       while ($depth > 0) {
           if($curRes == $iterator->BEGIN_MAP) { $depth++; }
           if($curRes == $iterator->END_MAP) { $depth--; }
   
           if (ref($curRes) && $curRes->is_problem()) {
       my $parts = $curRes->parts();
               my $title = $curRes->compTitle();
       my $symbx = $curRes->symb();
       $studentTable.=
    &Apache::loncommon::start_data_table_row().
    '<td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' 
                           : '<br />('.&mt('[_1]&nbsp;parts)',
    scalar(@{$parts}))
    ).
    '</td>';
       $studentTable.='<td valign="top">';
       my %form = ('CODE' => $env{'form.CODE'},);
       if ($env{'form.vProb'} eq 'yes' ) {
    $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
        undef,'both',\%form);
     } else {      } else {
                 $request->print('<h1>Not a valid DocID: '.$token.'</h1>');   my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
    $companswer =~ s|<form(.*?)>||g;
    $companswer =~ s|</form>||g;
   # while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
   #    $companswer =~ s/$1/ /ms;
   #    $request->print('match='.$1."<br />\n");
   # }
   # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
    $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;'.&mt('<b>Correct answer:</b><br />[_1]',$companswer);
       }
   
       my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
   
       if ($env{'form.lastSub'} eq 'datesub') {
    if ($record{'version'} eq '') {
       $studentTable.='<br />&nbsp;<span class="LC_warning">'.&mt('No recorded submission for this problem.').'</span><br />';
    } else {
       my %responseType = ();
       foreach my $partid (@{$parts}) {
    my @responseIds =$curRes->responseIds($partid);
    my @responseType =$curRes->responseType($partid);
    my %responseIds;
    for (my $i=0;$i<=$#responseIds;$i++) {
       $responseIds{$responseIds[$i]}=$responseType[$i];
    }
    $responseType{$partid} = \%responseIds;
       }
       $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
   
    }
       } elsif ($env{'form.lastSub'} eq 'all') {
    my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
    $studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
    $env{'request.course.id'},
    '','.submission');
    
       }
       if (&canmodify($usec)) {
    foreach my $partid (@{$parts}) {
       $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
       $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
       $question++;
    }
    $prob++;
       }
       $studentTable.='</td></tr>';
   
    }
           $curRes = $iterator->next();
       }
   
       $studentTable.='</table>'."\n".
    '<input type="button" value="'.&mt('Save').'" '.
    'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
    '</form>'."\n";
       $studentTable.=&show_grading_menu_form($symb);
       $request->print($studentTable);
   
       return '';
   }
   
   sub displaySubByDates {
       my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
       my $isCODE=0;
       my $isTask = ($symb =~/\.task$/);
       if (exists($record->{'resource.CODE'})) { $isCODE=1; }
       my $studentTable=&Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th>'.&mt('Date/Time').'</th>'.
    ($isCODE?'<th>'.&mt('CODE').'</th>':'').
    '<th>'.&mt('Submission').'</th>'.
    '<th>'.&mt('Status').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
       my ($version);
       my %mark;
       my %orders;
       $mark{'correct_by_student'} = $checkIcon;
       if (!exists($$record{'1:timestamp'})) {
    return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br />';
       }
   
       my $interaction;
       for ($version=1;$version<=$$record{'version'};$version++) {
    my $timestamp = 
       &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
    if (exists($$record{$version.':resource.0.version'})) {
       $interaction = $$record{$version.':resource.0.version'};
    }
   
    my $where = ($isTask ? "$version:resource.$interaction"
                : "$version:resource");
    $studentTable.=&Apache::loncommon::start_data_table_row().
       '<td>'.$timestamp.'</td>';
    if ($isCODE) {
       $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
    }
    my @versionKeys = split(/\:/,$$record{$version.':keys'});
    my @displaySub = ();
    foreach my $partid (@{$parts}) {
       my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
               : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
       
   
   #    next if ($$record{"$version:resource.$partid.solved"} eq '');
       my $display_part=&get_display_part($partid,$symb);
       foreach my $matchKey (@matchKey) {
    if (exists($$record{$version.':'.$matchKey}) &&
       $$record{$version.':'.$matchKey} ne '') {
   
       my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
                  : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
       $displaySub[0].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.'&nbsp;';
       $displaySub[0].='<span class="LC_internal_info">('.&mt('ID').'&nbsp;'.
    $responseId.')</span>&nbsp;<b>';
       if ($$record{"$where.$partid.tries"} eq '') {
    $displaySub[0].=&mt('Trial&nbsp;not&nbsp;counted');
       } else {
    $displaySub[0].=&mt('Trial&nbsp;[_1]',
       $$record{"$where.$partid.tries"});
       }
       my $responseType=($isTask ? 'Task'
                                                 : $responseType->{$partid}->{$responseId});
       if (!exists($orders{$partid})) { $orders{$partid}={}; }
       if (!exists($orders{$partid}->{$responseId})) {
    $orders{$partid}->{$responseId}=
       &get_order($partid,$responseId,$symb,$uname,$udom);
       }
       $displaySub[0].='</b>&nbsp; '.
    &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />';
    }
       }
       if (exists($$record{"$where.$partid.checkedin"})) {
    $displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
       $$record{"$where.$partid.checkedin"},
       $$record{"$where.$partid.checkedin.slot"}).
    '<br />';
       }
       if (exists $$record{"$where.$partid.award"}) {
    $displaySub[1].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.' &nbsp;'.
       lc($$record{"$where.$partid.award"}).' '.
       $mark{$$record{"$where.$partid.solved"}}.
       '<br />';
       }
       if (exists $$record{"$where.$partid.regrader"}) {
    $displaySub[2].=$$record{"$where.$partid.regrader"}.
       ' (<b>'.&mt('Part').':</b> '.$display_part.')';
       } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
    $displaySub[2].=
       $$record{"$version:resource.$partid.regrader"}.
       ' (<b>'.&mt('Part').':</b> '.$display_part.')';
       }
    }
    # needed because old essay regrader has not parts info
    if (exists $$record{"$version:resource.regrader"}) {
       $displaySub[2].=$$record{"$version:resource.regrader"};
    }
    $studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
    if ($displaySub[2]) {
       $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
    }
    $studentTable.='&nbsp;</td>'.
       &Apache::loncommon::end_data_table_row();
       }
       $studentTable.=&Apache::loncommon::end_data_table();
       return $studentTable;
   }
   
   sub updateGradeByPage {
       my ($request) = shift;
   
       my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
       my $cnum      = $env{"course.$env{'request.course.id'}.num"};
       my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
       my $pageTitle = $env{'form.page'};
       my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
       my ($uname,$udom) = split(/:/,$env{'form.student'});
       my $usec=$classlist->{$env{'form.student'}}[5];
       if (!&canmodify($usec)) {
    $request->print('<span class="LC_warning">Unable to modify requested student.('.$env{'form.student'}.'</span>');
    $request->print(&show_grading_menu_form($env{'form.symb'}));
    return;
       }
       my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
       $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
    '</h3>'."\n";
   
       $request->print($result);
   
       my $navmap = Apache::lonnavmaps::navmap->new();
       my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
       my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
       if (!$map) {
    $request->print('<span class="LC_warning">Unable to grade requested sequence. ('.$resUrl.')</span>');
    my ($symb)=&get_symb($request);
    $request->print(&show_grading_menu_form($symb));
    return; 
       }
       my $iterator = $navmap->getIterator($map->map_start(),
    $map->map_finish());
   
       my $studentTable=
    &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th align="center">&nbsp;'.&mt('Prob.').'&nbsp;</th>'.
    '<th>&nbsp;'.&mt('Title').'&nbsp;</th>'.
    '<th>&nbsp;'.&mt('Previous Score').'&nbsp;</th>'.
    '<th>&nbsp;'.&mt('New Score').'&nbsp;</th>'.
    &Apache::loncommon::end_data_table_header_row();
   
       $iterator->next(); # skip the first BEGIN_MAP
       my $curRes = $iterator->next(); # for "current resource"
       my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
       while ($depth > 0) {
           if($curRes == $iterator->BEGIN_MAP) { $depth++; }
           if($curRes == $iterator->END_MAP) { $depth--; }
   
           if (ref($curRes) && $curRes->is_problem()) {
       my $parts = $curRes->parts();
               my $title = $curRes->compTitle();
       my $symbx = $curRes->symb();
       $studentTable.=
    &Apache::loncommon::start_data_table_row().
    '<td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' 
                                           : '<br />('.&mt('[quant,_1,&nbsp;parts]',scalar(@{$parts}))
    ).')</td>';
       $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
   
       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};
   
    my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
       $env{'form.WGT'.$question.'_'.$partid} : 1;
    my $partial = $newpts/$wgt;
    my $score;
    if ($partial > 0) {
       $score = 'correct_by_override';
    } elsif ($newpts ne '') { #empty is taken as 0
       $score = 'incorrect_by_override';
    }
    my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
    if ($dropMenu eq 'excused') {
       $partial = '';
       $score = 'excused';
    } elsif ($dropMenu eq 'reset status'
    && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
       $newrecord{'resource.'.$partid.'.tries'} = 0;
       $newrecord{'resource.'.$partid.'.solved'} = '';
       $newrecord{'resource.'.$partid.'.award'} = '';
       $newrecord{'resource.'.$partid.'.awarded'} = 0;
       $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,$curRes->symb());
    my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
    $displayPts[0].='&nbsp;<b>Part:</b> '.$display_part.' = '.
       (($oldstatus eq 'excused') ? 'excused' : $oldpts).
       '&nbsp;<br />';
    $displayPts[1].='&nbsp;<b>Part:</b> '.$display_part.' = '.
        (($score eq 'excused') ? 'excused' : $newpts).
       '&nbsp;<br />';
    $question++;
    next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
   
    $newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';
    $newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';
    $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
       if (scalar(keys(%newrecord)) > 0);
   
    $changeflag++;
       }
       if (scalar(keys(%newrecord)) > 0) {
    my %record = 
       &Apache::lonnet::restore($symbx,$env{'request.course.id'},
        $udom,$uname);
   
    if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
       $newrecord{'resource.CODE'} = $env{'form.CODE'};
    } elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
       $newrecord{'resource.CODE'} = '';
    }
    &Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
    $udom,$uname);
    %record = &Apache::lonnet::restore($symbx,
      $env{'request.course.id'},
      $udom,$uname);
    &check_and_remove_from_queue($parts,\%record,undef,$symbx,
        $cdom,$cnum,$udom,$uname);
       }
       
               if ($aggregateflag) {
                   &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
                         $env{'course.'.$env{'request.course.id'}.'.domain'},
                         $env{'course.'.$env{'request.course.id'}.'.num'});
             }              }
  } else {  
              $request->print(&Apache::lonxml::tokeninputfield());      $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
          }   '<td valign="top">'.$displayPts[1].'</td>'.
      }   &Apache::loncommon::end_data_table_row();
   } else {  
     #&Apache::lonhomework::showhashsubset(\%ENV,'^form');      $prob++;
     $Apache::grades::viewgrades=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'});   }
     if ($command eq 'submission') {          $curRes = $iterator->next();
       &listStudents($request) if ($ENV{'form.student'} eq '');      }
       &submission($request,0,0) if ($ENV{'form.student'} ne '');  
     } elsif ($command eq 'processGroup') {      $studentTable.=&Apache::loncommon::end_data_table();
       &processGroup($request);      $studentTable.=&show_grading_menu_form($env{'form.symb'});
     } elsif ($command eq 'gradingmenu') {      my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
       $request->print(&gradingmenu($request));    'The scores were changed for '.
     } elsif ($command eq 'viewgrades') {    $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));
       $request->print(&viewgrades($request));      $request->print($grademsg.$studentTable);
     } elsif ($command eq 'handgrade') {  
       $request->print(&processHandGrade($request));      return '';
     } elsif ($command eq 'editgrades') {  }
       $request->print(&editgrades($request));  
     } elsif ($command eq 'verify') {  #-------- end of section for handling grading by page/sequence ---------
       $request->print(&verifyreceipt($request));  #
     } elsif ($command eq 'csvupload') {  #-------------------------------------------------------------------
       $request->print(&csvupload($request));  
     } elsif ($command eq 'csvuploadmap') {  #--------------------Scantron Grading-----------------------------------
       $request->print(&csvuploadmap($request));  #
 #    } elsif ($command eq 'receiptInput') {  #------ start of section for handling grading by page/sequence ---------
 #      &receiptInput($request);  
     } elsif ($command eq 'csvuploadassign') {  =pod
       if ($ENV{'form.associate'} ne 'Reverse Association') {  
  $request->print(&csvuploadassign($request));  =head1 Bubble sheet grading routines
       } else {  
  if ( $ENV{'form.upfile_associate'} ne 'reverse' ) {    For this documentation:
   $ENV{'form.upfile_associate'} = 'reverse';  
      'scanline' refers to the full line of characters
      from the file that we are parsing that represents one entire sheet
   
      'bubble line' refers to the data
      representing the line of bubbles that are on the physical bubble sheet
   
   
   The overall process is that a scanned in bubble sheet data is uploaded
   into a course. When a user wants to grade, they select a
   sequence/folder of resources, a file of bubble sheet info, and pick
   one of the predefined configurations for what each scanline looks
   like.
   
   Next each scanline is checked for any errors of either 'missing
   bubbles' (it's an error because it may have been mis-scanned
   because too light bubbling), 'double bubble' (each bubble line should
   have no more that one letter picked), invalid or duplicated CODE,
   invalid student ID
   
   If the CODE option is used that determines the randomization of the
   homework problems, either way the student ID is looked up into a
   username:domain.
   
   During the validation phase the instructor can choose to skip scanlines. 
   
   After the validation phase, there are now 3 bubble sheet files
   
     scantron_original_filename (unmodified original file)
     scantron_corrected_filename (file where the corrected information has replaced the original information)
     scantron_skipped_filename (contains the exact text of scanlines that where skipped)
   
   Also there is a separate hash nohist_scantrondata that contains extra
   correction information that isn't representable in the bubble sheet
   file (see &scantron_getfile() for more information)
   
   After all scanlines are either valid, marked as valid or skipped, then
   foreach line foreach problem in the picked sequence, an ssi request is
   made that simulates a user submitting their selected letter(s) against
   the homework problem.
   
   =over 4
   
   
   
   =item defaultFormData
   
     Returns html hidden inputs used to hold context/default values.
   
    Arguments:
     $symb - $symb of the current resource 
   
   =cut
   
   sub defaultFormData {
       my ($symb)=@_;
       return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
        '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
        '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
   }
   
   
   =pod 
   
   =item getSequenceDropDown
   
      Return html dropdown of possible sequences to grade
    
    Arguments:
      $symb - $symb of the current resource 
   
   =cut
   
   sub getSequenceDropDown {
       my ($symb)=@_;
       my $result='<select name="selectpage">'."\n";
       my ($titles,$symbx) = &getSymbMap();
       my ($curpage)=&Apache::lonnet::decode_symb($symb); 
       my $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $result.='<option value="'.$$symbx{$_}.'" '.
       ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
       '>'.$showtitle.'</option>'."\n";
    $ctr++;
       }
       $result.= '</select>';
       return $result;
   }
   
   
   =pod 
   
   =item scantron_filenames
   
      Returns a list of the scantron files in the current course 
   
   =cut
   
   sub scantron_filenames {
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
       &propath($cdom,$cname));
       my @possiblenames;
       foreach my $filename (sort(@files)) {
    ($filename)=split(/&/,$filename);
    if ($filename!~/^scantron_orig_/) { next ; }
    $filename=~s/^scantron_orig_//;
    push(@possiblenames,$filename);
       }
       return @possiblenames;
   }
   
   =pod 
   
   =item scantron_uploads
   
      Returns  html drop-down list of scantron files in current course.
   
    Arguments:
      $file2grade - filename to set as selected in the dropdown
   
   =cut
   
   sub scantron_uploads {
       my ($file2grade) = @_;
       my $result= '<select name="scantron_selectfile">';
       $result.="<option></option>";
       foreach my $filename (sort(&scantron_filenames())) {
    $result.="<option".($filename eq $file2grade ? ' selected="selected"':'').">$filename</option>\n";
       }
       $result.="</select>";
       return $result;
   }
   
   =pod 
   
   =item scantron_scantab
   
     Returns html drop down of the scantron formats in the scantronformat.tab
     file.
   
   =cut
   
   sub scantron_scantab {
       my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
       my $result='<select name="scantron_format">'."\n";
       $result.='<option></option>'."\n";
       foreach my $line (<$fh>) {
    my ($name,$descrip)=split(/:/,$line);
    if ($name =~ /^\#/) { next; }
    $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
       }
       $result.='</select>'."\n";
   
       return $result;
   }
   
   =pod 
   
   =item scantron_CODElist
   
     Returns html drop down of the saved CODE lists from current course,
     generated from earlier printings.
   
   =cut
   
   sub scantron_CODElist {
       my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
       my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
       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>';
       return $namechoice;
   }
   
   =pod 
   
   =item scantron_CODEunique
   
     Returns the html for "Each CODE to be used once" radio.
   
   =cut
   
   sub scantron_CODEunique {
       my $result='<span style="white-space: nowrap;">
                    <label><input type="radio" name="scantron_CODEunique"
                           value="yes" checked="checked" />'.&mt('Yes').' </label>
                   </span>
                   <span style="white-space: nowrap;">
                    <label><input type="radio" name="scantron_CODEunique"
                           value="no" />'.&mt('No').' </label>
                   </span>';
       return $result;
   }
   
   =pod 
   
   =item scantron_selectphase
   
     Generates the initial screen to start the bubble sheet process.
     Allows for - starting a grading run.
                - downloading existing scan data (original, corrected
                                                   or skipped info)
   
                - uploading new scan data
   
    Arguments:
     $r          - The Apache request object
     $file2grade - name of the file that contain the scanned data to score
   
   =cut
   
   sub scantron_selectphase {
       my ($r,$file2grade) = @_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $sequence_selector=&getSequenceDropDown($symb);
       my $default_form_data=&defaultFormData($symb);
       my $grading_menu_button=&show_grading_menu_form($symb);
       my $file_selector=&scantron_uploads($file2grade);
       my $format_selector=&scantron_scantab();
       my $CODE_selector=&scantron_CODElist();
       my $CODE_unique=&scantron_CODEunique();
       my $result;
   
       # Chunk of form to prompt for a file to grade and how:
   
       $result.= '
       <br />
       <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
       <input type="hidden" name="command" value="scantron_warning" />
       '.$default_form_data.'
       '.&Apache::loncommon::start_data_table('LC_scantron_action').'
          '.&Apache::loncommon::start_data_table_header_row().'
               <th colspan="2">
                 &nbsp;Specify file and which Folder/Sequence to grade
               </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Sequence to grade: </td><td> '.$sequence_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Filename of scoring office file: </td><td> '.$file_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Format of data file: </td><td> '.$format_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Saved CODEs to validate against: </td><td> '.$CODE_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Each CODE is only to be used once:</td><td> '.$CODE_unique.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
       <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 existing corrections</label> <br />
                  <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label>
       </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td colspan="2">
                 <input type="submit" value="Grading: Validate Scantron Records" />
               </td>
          '.&Apache::loncommon::end_data_table_row().'
       '.&Apache::loncommon::end_data_table().'
       </form>
   ';
      
       $r->print($result);
   
       if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
           &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
   
    # Chunk of form to prompt for a scantron file upload.
   
           $r->print('
       <br />
       '.&Apache::loncommon::start_data_table('LC_scantron_action').'
          '.&Apache::loncommon::start_data_table_header_row().'
               <th>
                 &nbsp;Specify a Scantron data file to upload.
               </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td>
   ');
       my $default_form_data=&defaultFormData(&get_symb($r,1));
       my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
       $r->print(<<UPLOAD);
                 <script type="text/javascript" language="javascript">
       function checkUpload(formname) {
    if (formname.upfile.value == "") {
       alert("Please use the browse button to select a file from your local directory.");
       return false;
    }
    formname.submit();
       }
                 </script>
   
                 <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
                   $default_form_data
                   <input name='courseid' type='hidden' value='$cnum' />
                   <input name='domainid' type='hidden' value='$cdom' />
                   <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
   
           $r->print('
               </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::end_data_table().'
   ');
       }
   
       # Chunk of the form that prompts to view a scoring office file,
       # corrected file, skipped records in a file.
   
       $r->print('
      <br />
      <form action="/adm/grades" name="scantron_download">
        '.$default_form_data.'
        <input type="hidden" name="command" value="scantron_download" />
        '.&Apache::loncommon::start_data_table('LC_scantron_action').'
          '.&Apache::loncommon::start_data_table_header_row().'
                 <th>
                   &nbsp;Download a scoring office file
                 </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
                 <td> Filename of scoring office file: '.$file_selector.' 
                   <br />
                   <input type="submit" value="Download: Show List of Associated Files" />
          '.&Apache::loncommon::end_data_table_row().'
        '.&Apache::loncommon::end_data_table().'
      </form>
      <br />
   ');
   
       &Apache::lonpickcode::code_list($r,2);
       $r->print($grading_menu_button);
       return
   }
   
   =pod
   
   =item get_scantron_config
   
      Parse and return the scantron configuration line selected as a
      hash of configuration file fields.
   
    Arguments:
       which - the name of the configuration to parse from the file.
   
   
    Returns:
               If the named configuration is not in the file, an empty
               hash is returned.
       a hash with the fields
         name         - internal name for the this configuration setup
         description  - text to display to operator that describes this config
         CODElocation - if 0 or the string 'none'
                             - no CODE exists for this config
                        if -1 || the string 'letter'
                             - a CODE exists for this config and is
                               a string of letters
                        Unsupported value (but planned for future support)
                             if a positive integer
                                  - The CODE exists as the first n items from
                                    the question section of the form
                             if the string 'number'
                                  - The CODE exists for this config and is
                                    a string of numbers
         CODEstart   - (only matter if a CODE exists) column in the line where
                        the CODE starts
         CODElength  - length of the CODE
         IDstart     - column where the student ID number starts
         IDlength    - length of the student ID info
         Qstart      - column where the information from the bubbled
                       'questions' start
         Qlength     - number of columns comprising a single bubble line from
                       the sheet. (usually either 1 or 10)
         Qon         - either a single character representing the character used
                       to signal a bubble was chosen in the positional setup, or
                       the string 'letter' if the letter of the chosen bubble is
                       in the final, or 'number' if a number representing the
                       chosen bubble is in the file (1->A 0->J)
         Qoff        - the character used to represent that a bubble was
                       left blank
         PaperID     - if the scanning process generates a unique number for each
                       sheet scanned the column that this ID number starts in
         PaperIDlength - number of columns that comprise the unique ID number
                         for the sheet of paper
         FirstName   - column that the first name starts in
         FirstNameLength - number of columns that the first name spans
    
         LastName    - column that the last name starts in
         LastNameLength - number of columns that the last name spans
   
   =cut
   
   sub get_scantron_config {
       my ($which) = @_;
       my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
       my %config;
       #FIXME probably should move to XML it has already gotten a bit much now
       foreach my $line (<$fh>) {
    my ($name,$descrip)=split(/:/,$line);
    if ($name ne $which ) { next; }
    chomp($line);
    my @config=split(/:/,$line);
    $config{'name'}=$config[0];
    $config{'description'}=$config[1];
    $config{'CODElocation'}=$config[2];
    $config{'CODEstart'}=$config[3];
    $config{'CODElength'}=$config[4];
    $config{'IDstart'}=$config[5];
    $config{'IDlength'}=$config[6];
    $config{'Qstart'}=$config[7];
    $config{'Qlength'}=$config[8];
    $config{'Qoff'}=$config[9];
    $config{'Qon'}=$config[10];
    $config{'PaperID'}=$config[11];
    $config{'PaperIDlength'}=$config[12];
    $config{'FirstName'}=$config[13];
    $config{'FirstNamelength'}=$config[14];
    $config{'LastName'}=$config[15];
    $config{'LastNamelength'}=$config[16];
    last;
       }
       return %config;
   }
   
   =pod 
   
   =item username_to_idmap
   
       creates a hash keyed by student id with values of the corresponding
       student username:domain.
   
     Arguments:
   
       $classlist - reference to the class list hash. This is a hash
                    keyed by student name:domain  whose elements are references
                    to arrays containing various chunks of information
                    about the student. (See loncoursedata for more info).
   
     Returns
       %idmap - the constructed hash
   
   =cut
   
   sub username_to_idmap {
       my ($classlist)= @_;
       my %idmap;
       foreach my $student (keys(%$classlist)) {
    $idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}=
       $student;
       }
       return %idmap;
   }
   
   =pod
   
   =item scantron_fixup_scanline
   
      Process a requested correction to a scanline.
   
     Arguments:
       $scantron_config   - hash from &get_scantron_config()
       $scan_data         - hash of correction information 
                             (see &scantron_getfile())
       $line              - existing scanline
       $whichline         - line number of the passed in scanline
       $field             - type of change to process 
                            (either 
                             'ID'     -> correct the student ID number
                             'CODE'   -> correct the CODE
                             'answer' -> fixup the submitted answers)
       
      $args               - hash of additional info,
                             - 'ID' 
                                  'newid' -> studentID to use in replacement
                                             of existing one
                             - 'CODE' 
                                  'CODE_ignore_dup' - set to true if duplicates
                                                      should be ignored.
                          'CODE' - is new code or 'use_unfound'
                                           if the existing unfound code should
                                           be used as is
                             - 'answer'
                                  'response' - new answer or 'none' if blank
                                  'question' - the bubble line to change
   
     Returns:
       $line - the modified scanline
   
     Side effects: 
       $scan_data - may be updated
   
   =cut
   
   
   sub scantron_fixup_scanline {
       my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
       
       
       if ($field eq 'ID') {
    if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
       return ($line,1,'New value too large');
    }
    if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
       $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
        $args->{'newid'});
    }
    substr($line,$$scantron_config{'IDstart'}-1,
          $$scantron_config{'IDlength'})=$args->{'newid'};
    if ($args->{'newid'}=~/^\s*$/) {
       &scan_data($scan_data,"$whichline.user",
          $args->{'username'}.':'.$args->{'domain'});
    }
       } elsif ($field eq 'CODE') {
    if ($args->{'CODE_ignore_dup'}) {
       &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
    }
    &scan_data($scan_data,"$whichline.useCODE",'1');
    if ($args->{'CODE'} ne 'use_unfound') {
       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'};
    }
       } elsif ($field eq 'answer') {
    &scantron_get_maxbubble(); # Need the bubble counter info.
    my $length =$scantron_config->{'Qlength'};
    my $off=$scantron_config->{'Qoff'};
    my $on=$scantron_config->{'Qon'};
           my $question_number = $args->{'question'} -1;
           my $first_position  = $first_bubble_line{$question_number};
    my $bubble_count    = $bubble_lines_per_response{$question_number};
           my $bubbles_per_line= $$scantron_config{'Qlength'};
    my $answer=${off}x($bubbles_per_line*$bubble_count);
           my $final_answer;
           if ($$scantron_config{'Qon'} eq 'letter'  ||
       $$scantron_config{'Qon'} eq 'number') { 
       $bubbles_per_line = 10;
    }
    if (defined $args->{'response'}) {
       
       if ($args->{'response'} eq 'none') {
    &scan_data($scan_data,
      "$whichline.no_bubble.".$args->{'question'},'1');
       } else {
    my ($bubble_line, $bubble_number) = split(/:/,$args->{'response'});
    if ($on eq 'letter') {
       my @alphabet=('A'..'Z');
       $answer=$alphabet[$bubble_number];
    } elsif ($on eq 'number') {
       $answer= $bubble_number+1;
       if ($answer == 10) { $answer = '0'; }
    } else {
       substr($answer,$bubble_number+$bubble_line*$bubbles_per_line,1)=$on;
       $final_answer = $answer;
    }
    &scan_data($scan_data,
      "$whichline.no_bubble.".$args->{'question'},undef,'1');
   
    # Positional notation already has the right final answer length..
   
    if (($on eq 'letter') || ($on eq 'number')) {
       for (my $l = 0; $l < $bubble_count; $l++) {
    if ($l eq $bubble_line) {
       $final_answer .= $answer;
    } else {
       $final_answer .= ' ';
    }
       }
    }
       }
       # $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
       #substr($line,$where-1,$length)=$answer;
       substr($line, 
      $scantron_config->{'Qstart'}+$first_position-1,
      $bubbles_per_line*$length) = $final_answer;
    }
       }
       return $line;
   }
   
   =pod
   
   =item scan_data
   
       Edit or look up  an item in the scan_data hash.
   
     Arguments:
       $scan_data  - The hash (see scantron_getfile)
       $key        - shorthand of the key to edit (actual key is
                     scantronfilename_key).
       $data        - New value of the hash entry.
       $delete      - If true, the entry is removed from the hash.
   
     Returns:
       The new value of the hash table field (undefined if deleted).
   
   =cut
   
   
   sub scan_data {
       my ($scan_data,$key,$value,$delete)=@_;
       my $filename=$env{'form.scantron_selectfile'};
       if (defined($value)) {
    $scan_data->{$filename.'_'.$key} = $value;
       }
       if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
       return $scan_data->{$filename.'_'.$key};
   }
   
   =pod 
   
   =item scantron_parse_scanline
   
     Decodes a scanline from the selected scantron file
   
    Arguments:
       line             - The text of the scantron file line to process
       whichline        - Line number
       scantron_config  - Hash describing the format of the scantron lines.
       scan_data        - Hash of extra information about the scanline
                          (see scantron_getfile for more information)
       just_header      - True if should not process question answers but only
                          the stuff to the left of the answers.
    Returns:
      Hash containing the result of parsing the scanline
   
      Keys are all proceeded by the string 'scantron.'
   
          CODE    - the CODE in use for this scanline
          useCODE - 1 if the CODE is invalid but it usage has been forced
                    by the operator
          CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
                               CODEs were selected, but the usage has been
                               forced by the operator
          ID  - student ID
          PaperID - if used, the ID number printed on the sheet when the 
                    paper was scanned
          FirstName - first name from the sheet
          LastName  - last name from the sheet
   
        if just_header was not true these key may also exist
   
          missingerror - a list of bubble ranges that are considered to be answers
                         to a single question that don't have any bubbles filled in.
                         Of the form questionnumber:firstbubblenumber:count.
          doubleerror  - a list of bubble ranges that are considered to be answers
                         to a single question that have more than one bubble filled in.
                         Of the form questionnumber::firstbubblenumber:count
      
                   In the above, count is the number of bubble responses in the
                   input line needed to represent the possible answers to the question.
                   e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
                   per line would have count = 2.
   
          maxquest     - the number of the last bubble line that was parsed
   
          (<number> starts at 1)
          <number>.answer - zero or more letters representing the selected
                            letters from the scanline for the bubble line 
                            <number>.
                            if blank there was either no bubble or there where
                            multiple bubbles, (consult the keys missingerror and
                            doubleerror if this is an error condition)
   
   =cut
   
   sub scantron_parse_scanline {
       my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
   
       my %record;
       my $questions=substr($line,$$scantron_config{'Qstart'}-1);  # Answers
       my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
       if (!($$scantron_config{'CODElocation'} eq 0 ||
     $$scantron_config{'CODElocation'} eq 'none')) {
    if ($$scantron_config{'CODElocation'} < 0 ||
       $$scantron_config{'CODElocation'} eq 'letter' ||
       $$scantron_config{'CODElocation'} eq 'number') {
       $record{'scantron.CODE'}=substr($data,
       $$scantron_config{'CODEstart'}-1,
       $$scantron_config{'CODElength'});
       if (&scan_data($scan_data,"$whichline.useCODE")) {
    $record{'scantron.useCODE'}=1;
       }
       if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
    $record{'scantron.CODE_ignore_dup'}=1;
       }
  } else {   } else {
   $ENV{'form.upfile_associate'} = 'forward';      #FIXME interpret first N questions
  }   }
  $request->print(&csvuploadmap($request));      }
       $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
     $$scantron_config{'IDlength'});
       $record{'scantron.PaperID'}=
    substr($data,$$scantron_config{'PaperID'}-1,
          $$scantron_config{'PaperIDlength'});
       $record{'scantron.FirstName'}=
    substr($data,$$scantron_config{'FirstName'}-1,
          $$scantron_config{'FirstNamelength'});
       $record{'scantron.LastName'}=
    substr($data,$$scantron_config{'LastName'}-1,
          $$scantron_config{'LastNamelength'});
       if ($just_header) { return \%record; }
   
       my @alphabet=('A'..'Z');
       my $questnum=0;
       my $ansnum  =1; # Multiple 'answer lines'/question.
   
       chomp($questions); # Get rid of any trailing \n.
       $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
       while (length($questions)) {
    my $answers_needed = $bubble_lines_per_response{$questnum};
    my $answer_length  = $$scantron_config{'Qlength'} * $answers_needed;
   
   
   
    $questnum++;
    my $currentquest = substr($questions,0,$answer_length);
    $questions       = substr($questions,0,$answer_length)='';
    if (length($currentquest) < $answer_length) { next; }
   
    # Qon letter implies for each slot in currentquest we have:
    #    ? or * for doubles a letter in A-Z for a bubble and
           #    about anything else (esp. a value of Qoff for missing
    #    bubbles.
   
   
    if ($$scantron_config{'Qon'} eq 'letter') {
   
       if ($currentquest =~ /\?/
    || $currentquest =~ /\*/
    || (&occurence_count($currentquest, "[A-Z]") > 1)) {
    push(@{$record{'scantron.doubleerror'}},$questnum);
    for (my $ans = 0; $ans < $answers_needed; $ans++) { 
       my $bubble = substr($currentquest, $ans, 1);
       if ($bubble =~ /[A-Z]/ ) {
    $record{"scantron.$ansnum.answer"} = $bubble;
       } else {
    $record{"scantron.$ansnum.answer"}='';
       }
       $ansnum++;
    }
   
       } elsif (!defined($currentquest)
        || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest))
        || (&occurence_count($currentquest, "[A-Z]") == 0)) {
    for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
      #  $ansnum += $answers_needed;
    }
       } else {
    for (my $ans = 0; $ans < $answers_needed; $ans++) {
       $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
       $ansnum++;
    }
       }
   
    # Qon 'number' implies each slot gives a digit that indexes the
    #    the bubbles filled or Qoff or a non number for unbubbled lines.
           #    and *? for double bubbles on a line.
    #    these answers are also stored as letters.
   
    } elsif ($$scantron_config{'Qon'} eq 'number') {
       if ($currentquest =~ /\?/
    || $currentquest =~ /\*/
    || (&occurence_count($currentquest, '\d') > 1)) {
    push(@{$record{'scantron.doubleerror'}},$questnum);
    for (my $ans = 0; $ans < $answers_needed; $ans++) {
       my $bubble = substr($currentquest, $ans, 1);
       if ($bubble =~ /\d/) {
    $record{"scantron.$ansnum.answer"} = $alphabet[$bubble];
       } else {
    $record{"scantron.$ansnum.answer"}=' ';
       }
       $ansnum++;
    }
   
       } elsif (!defined($currentquest)
        || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest)) 
        || (&occurence_count($currentquest, '\d') == 0)) {
    for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
       $ansnum += $answers_needed;
    }
   
       } else {
    $currentquest = &digits_to_letters($currentquest);
    for (my $ans =0; $ans < $answers_needed; $ans++) {
       $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
       $ansnum++;
    }
       }
    } else {
   
       # Otherwise there's a positional notation;
       # each bubble line requires Qlength items, and there are filled in
       # bubbles for each case where there 'Qon' characters.
       #
   
       my @array=split($$scantron_config{'Qon'},$currentquest,-1);
   
       # If the split only  giveas us one element.. the full length of the
       # answser string, no bubbles are filled in:
   
       if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
    for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
    }
   
    #  If the bubble is not the last position, there will be
    # 2 elements.  If it is the last position, there will be 1 element.
   
       } elsif (scalar(@array) le 2) {
   
    my $location      = length($array[0]);
    my $line_num      = int($location / $$scantron_config{'Qlength'});
    my $bubble        = $alphabet[$location % $$scantron_config{'Qlength'}];
   
   
    for (my $ans = 0; $ans < $answers_needed; $ans++) {
       if ($ans eq $line_num) {
    $record{"scantron.$ansnum.answer"} = $bubble;
       } else {
    $record{"scantron.$ansnum.answer"} = ' ';
       }
       $ansnum++;
    }
       }
       #  If there's more than one instance of a bubble character
       #  That's a double bubble; with positional notation we can
       #  record all the bubbles filled in as well as the 
       #  fact this response consists of multiple bubbles.
       #
       else {
    push(@{$record{'scantron.doubleerror'}},$questnum);
   
    my $first_answer = $ansnum;
    for (my $ans =0; $ans < $answers_needed; $ans++) {
       my $item = $first_answer+$ans;
       $record{"scantron.$item.answer"} = '';
    }
   
    my @ans=@array;
    my $i=0;
    my $increment = 0;
    while ($#ans) {
       $i+=length($ans[0]) + $increment;
       my $line   = int($i/$$scantron_config{'Qlength'} + $first_answer);
       my $bubble = $i%$$scantron_config{'Qlength'};
       $record{"scantron.$line.answer"}.=$alphabet[$bubble];
       shift(@ans);
       $increment = 1;
    }
    $ansnum += $answers_needed;
       }
    }
       }
       $record{'scantron.maxquest'}=$questnum;
       return \%record;
   }
   
   =pod
   
   =item scantron_add_delay
   
      Adds an error message that occurred during the grading phase to a
      queue of messages to be shown after grading pass is complete
   
    Arguments:
      $delayqueue  - arrary ref of hash ref of error messages
      $scanline    - the scanline that caused the error
      $errormesage - the error message
      $errorcode   - a numeric code for the error
   
    Side Effects:
      updates the $delayqueue to have a new hash ref of the error
   
   =cut
   
   sub scantron_add_delay {
       my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
       push(@$delayqueue,
    {'line' => $scanline, 'emsg' => $errormessage,
     'ecode' => $errorcode }
    );
   }
   
   =pod
   
   =item scantron_find_student
   
      Finds the username for the current scanline
   
     Arguments:
      $scantron_record - hash result from scantron_parse_scanline
      $scan_data       - hash of correction information 
                         (see &scantron_getfile() form more information)
      $idmap           - hash from &username_to_idmap()
      $line            - number of current scanline
    
     Returns:
      Either 'username:domain' or undef if unknown
   
   =cut
   
   sub scantron_find_student {
       my ($scantron_record,$scan_data,$idmap,$line)=@_;
       my $scanID=$$scantron_record{'scantron.ID'};
       if ($scanID =~ /^\s*$/) {
     return &scan_data($scan_data,"$line.user");
       }
       foreach my $id (keys(%$idmap)) {
     if (lc($id) eq lc($scanID)) {
        return $$idmap{$id};
     }
       }
       return undef;
   }
   
   =pod
   
   =item scantron_filter
   
      Filter sub for lonnavmaps, filters out hidden resources if ignore
      hidden resources was selected
   
   =cut
   
   sub scantron_filter {
       my ($curres)=@_;
   
       if (ref($curres) && $curres->is_problem()) {
    # if the user has asked to not have either hidden
    # or 'randomout' controlled resources to be graded
    # don't include them
    if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
       && $curres->randomout) {
       return 0;
    }
    return 1;
       }
       return 0;
   }
   
   =pod
   
   =item scantron_process_corrections
   
      Gets correction information out of submitted form data and corrects
      the scanline
   
   =cut
   
   sub scantron_process_corrections {
       my ($r) = @_;
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
       my $classlist=&Apache::loncoursedata::get_classlist();
       my $which=$env{'form.scantron_line'};
       my $line=&scantron_get_line($scanlines,$scan_data,$which);
       my ($skip,$err,$errmsg);
       if ($env{'form.scantron_skip_record'}) {
    $skip=1;
       } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
    my $newstudent=$env{'form.scantron_username'}.':'.
       $env{'form.scantron_domain'};
    my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
    ($line,$err,$errmsg)=
       &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
        'ID',{'newid'=>$newid,
       'username'=>$env{'form.scantron_username'},
       'domain'=>$env{'form.scantron_domain'}});
       } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
    my $resolution=$env{'form.scantron_CODE_resolution'};
    my $newCODE;
    my %args;
    if      ($resolution eq 'use_unfound') {
       $newCODE='use_unfound';
    } elsif ($resolution eq 'use_found') {
       $newCODE=$env{'form.scantron_CODE_selectedvalue'};
    } elsif ($resolution eq 'use_typed') {
       $newCODE=$env{'form.scantron_CODE_newvalue'};
    } elsif ($resolution =~ /^use_closest_(\d+)/) {
       $newCODE=$env{"form.scantron_CODE_closest_$1"};
    }
    if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
       $args{'CODE_ignore_dup'}=1;
    }
    $args{'CODE'}=$newCODE;
    ($line,$err,$errmsg)=
       &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
        'CODE',\%args);
       } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
    foreach my $question (split(',',$env{'form.scantron_questions'})) {
       ($line,$err,$errmsg)=
    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
    $which,'answer',
    { 'question'=>$question,
          'response'=>$env{"form.scantron_correct_Q_$question"}});
       if ($err) { last; }
    }
       }
       if ($err) {
    $r->print("<span class=\"LC_warning\">Unable to accept last correction, an error occurred :$errmsg:</span>");
       } else {
    &scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
    &scantron_putfile($scanlines,$scan_data);
       }
   }
   
   =pod
   
   =item reset_skipping_status
   
      Forgets the current set of remember skipped scanlines (and thus
      reverts back to considering all lines in the
      scantron_skipped_<filename> file)
   
   =cut
   
   sub reset_skipping_status {
       my ($scanlines,$scan_data)=&scantron_getfile();
       &scan_data($scan_data,'remember_skipping',undef,1);
       &scantron_putfile(undef,$scan_data);
   }
   
   =pod
   
   =item start_skipping
   
      Marks a scanline to be skipped. 
   
   =cut
   
   sub start_skipping {
       my ($scan_data,$i)=@_;
       my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
       if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
    $remembered{$i}=2;
       } else {
    $remembered{$i}=1;
       }
       &scan_data($scan_data,'remember_skipping',join(':',%remembered));
   }
   
   =pod
   
   =item should_be_skipped
   
      Checks whether a scanline should be skipped.
   
   =cut
   
   sub should_be_skipped {
       my ($scanlines,$scan_data,$i)=@_;
       if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
    # not redoing old skips
    if ($scanlines->{'skipped'}[$i]) { return 1; }
    return 0;
       }
       my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
   
       if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
    return 0;
       }
       return 1;
   }
   
   =pod
   
   =item remember_current_skipped
   
      Discovers what scanlines are in the scantron_skipped_<filename>
      file and remembers them into scan_data for later use.
   
   =cut
   
   sub remember_current_skipped {
       my ($scanlines,$scan_data)=&scantron_getfile();
       my %to_remember;
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    if ($scanlines->{'skipped'}[$i]) {
       $to_remember{$i}=1;
    }
       }
   
       &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
       &scantron_putfile(undef,$scan_data);
   }
   
   =pod
   
   =item check_for_error
   
       Checks if there was an error when attempting to remove a specific
       scantron_.. bubble sheet data file. Prints out an error if
       something went wrong.
   
   =cut
   
   sub check_for_error {
       my ($r,$result)=@_;
       if ($result ne 'ok' && $result ne 'not_found' ) {
    $r->print("An error occurred ($result) when trying to Remove the existing corrections.");
       }
   }
   
   =pod
   
   =item scantron_warning_screen
   
      Interstitial screen to make sure the operator has selected the
      correct options before we start the validation phase.
   
   =cut
   
   sub scantron_warning_screen {
       my ($button_text)=@_;
       my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my $CODElist;
       if ($scantron_config{'CODElocation'} &&
    $scantron_config{'CODEstart'} &&
    $scantron_config{'CODElength'}) {
    $CODElist=$env{'form.scantron_CODElist'};
    if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">None</span>'; }
    $CODElist=
       '<tr><td><b>List of CODES to validate against:</b></td><td><tt>'.
       $env{'form.scantron_CODElist'}.'</tt></td></tr>';
       }
       return (<<STUFF);
   <p>
   <span class="LC_warning">Please double check the information
                    below before clicking on '$button_text'</span>
   </p>
   <table>
   <tr><td><b>Sequence to be Graded:</b></td><td>$title</td></tr>
   <tr><td><b>Data File that will be used:</b></td><td><tt>$env{'form.scantron_selectfile'}</tt></td></tr>
   $CODElist
   </table>
   <br />
   <p> If this information is correct, please click on '$button_text'.</p>
   <p> If something is incorrect, please click the 'Grading Menu' button to start over.</p>
   
   <br />
   STUFF
   }
   
   =pod
   
   =item scantron_do_warning
   
      Check if the operator has picked something for all required
      fields. Error out if something is missing.
   
   =cut
   
   sub scantron_do_warning {
       my ($r)=@_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $default_form_data=&defaultFormData($symb);
       $r->print(&scantron_form_start().$default_form_data);
       if ( $env{'form.selectpage'} eq '' ||
    $env{'form.scantron_selectfile'} eq '' ||
    $env{'form.scantron_format'} eq '' ) {
    $r->print("<p>You have forgetten to specify some information. Please go Back and try again.</p>");
    if ( $env{'form.selectpage'} eq '') {
       $r->print('<p><span class="LC_error">You have not selected a Sequence to grade</span></p>');
    } 
    if ( $env{'form.scantron_selectfile'} eq '') {
       $r->print('<p><span class="LC_error">You have not selected a file that contains the student\'s response data.</span></p>');
    } 
    if ( $env{'form.scantron_format'} eq '') {
       $r->print('<p><span class="LC_error">You have not selected a the format of the student\'s response data.</span></p>');
    } 
       } else {
    my $warning=&scantron_warning_screen('Grading: Validate Records');
    $r->print(<<STUFF);
   $warning
   <input type="submit" name="submit" value="Grading: Validate Records" />
   <input type="hidden" name="command" value="scantron_validate" />
   STUFF
       }
       $r->print("</form><br />".&show_grading_menu_form($symb));
       return '';
   }
   
   =pod
   
   =item scantron_form_start
   
       html hidden input for remembering all selected grading options
   
   =cut
   
   sub scantron_form_start {
       my ($max_bubble)=@_;
       my $result= <<SCANTRONFORM;
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
     <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" />
     <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" />
     <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" />
     <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
     <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" />
     <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" />
     <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" />
     <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
     <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
   SCANTRONFORM
   
     my $line = 0;
       while (defined($env{"form.scantron.bubblelines.$line"})) {
          my $chunk =
      '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
          $chunk .=
      '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
          $result .= $chunk;
          $line++;
      }
       return $result;
   }
   
   =pod
   
   =item scantron_validate_file
   
       Dispatch routine for doing validation of a bubble sheet data file.
   
       Also processes any necessary information resets that need to
       occur before validation begins (ignore previous corrections,
       restarting the skipped records processing)
   
   =cut
   
   sub scantron_validate_file {
       my ($r) = @_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $default_form_data=&defaultFormData($symb);
       
       # do the detection of only doing skipped records first befroe we delete
       # them when doing the corrections reset
       if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
    &reset_skipping_status();
       }
       if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
    &remember_current_skipped();
    $env{'form.scantron_options_redo'}='redo_skipped_ready';
       }
   
       if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
    &check_for_error($r,&scantron_remove_file('corrected'));
    &check_for_error($r,&scantron_remove_file('skipped'));
    &check_for_error($r,&scantron_remove_scan_data());
    $env{'form.scantron_options_ignore'}='done';
       }
   
       if ($env{'form.scantron_corrections'}) {
    &scantron_process_corrections($r);
       }
       $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();
       my $result=&scantron_form_start($max_bubble).$default_form_data;
       $r->print($result);
       
       my @validate_phases=( 'sequence',
     'ID',
     'CODE',
     'doublebubble',
     'missingbubbles');
       if (!$env{'form.validatepass'}) {
    $env{'form.validatepass'} = 0;
       }
       my $currentphase=$env{'form.validatepass'};
   
   
       my $stop=0;
       while (!$stop && $currentphase < scalar(@validate_phases)) {
    $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");
    $r->rflush();
    my $which="scantron_validate_".$validate_phases[$currentphase];
    {
       no strict 'refs';
       ($stop,$currentphase)=&$which($r,$currentphase);
    }
       }
       if (!$stop) {
    my $warning=&scantron_warning_screen('Start Grading');
    $r->print(<<STUFF);
   Validation process complete.<br />
   $warning
   <input type="submit" name="submit" value="Start Grading" />
   <input type="hidden" name="command" value="scantron_process" />
   STUFF
   
       } else {
    $r->print('<input type="hidden" name="command" value="scantron_validate" />');
    $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
       }
       if ($stop) {
    if ($validate_phases[$currentphase] eq 'sequence') {
       $r->print('<input type="submit" name="submit" value="Ignore -> " />');
       $r->print(' this error <br />');
   
       $r->print(" <p>Or click the 'Grading Menu' button to start over.</p>");
    } else {
       $r->print('<input type="submit" name="submit" value="Continue ->" />');
       $r->print(' using corrected info <br />');
       $r->print("<input type='submit' value='Skip' name='scantron_skip_record' />");
       $r->print(" this scanline saving it for later.");
    }
       }
       $r->print(" </form><br />".&show_grading_menu_form($symb));
       return '';
   }
   
   
   =pod
   
   =item scantron_remove_file
   
      Removes the requested bubble sheet data file, makes sure that
      scantron_original_<filename> is never removed
   
   
   =cut
   
   sub scantron_remove_file {
       my ($which)=@_;
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my $file='scantron_';
       if ($which eq 'corrected' || $which eq 'skipped') {
    $file.=$which.'_';
       } else {
    return 'refused';
       }
       $file.=$env{'form.scantron_selectfile'};
       return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
   }
   
   
   =pod
   
   =item scantron_remove_scan_data
   
      Removes all scan_data correction for the requested bubble sheet
      data file.  (In the case that both the are doing skipped records we need
      to remember the old skipped lines for the time being so that element
      persists for a while.)
   
   =cut
   
   sub scantron_remove_scan_data {
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
       my @todelete;
       my $filename=$env{'form.scantron_selectfile'};
       foreach my $key (@keys) {
    if ($key=~/^\Q$filename\E_/) {
       if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
    $key=~/remember_skipping/) {
    next;
       }
       push(@todelete,$key);
    }
       }
       my $result;
       if (@todelete) {
    $result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname);
       }
       return $result;
   }
   
   
   =pod
   
   =item scantron_getfile
   
       Fetches the requested bubble sheet data file (all 3 versions), and
       the scan_data hash
     
     Arguments:
       None
   
     Returns:
       2 hash references
   
        - first one has 
            orig      -
            corrected -
            skipped   -  each of which points to an array ref of the specified
                         file broken up into individual lines
            count     - number of scanlines
    
        - second is the scan_data hash possible keys are
          ($number refers to scanline numbered $number and thus the key affects
           only that scanline
           $bubline refers to the specific bubble line element and the aspects
           refers to that specific bubble line element)
   
          $number.user - username:domain to use
          $number.CODE_ignore_dup 
                       - ignore the duplicate CODE error 
          $number.useCODE
                       - use the CODE in the scanline as is
          $number.no_bubble.$bubline
                       - it is valid that there is no bubbled in bubble
                         at $number $bubline
          remember_skipping
                       - a frozen hash containing keys of $number and values
                         of either 
                           1 - we are on a 'do skipped records pass' and plan
                               on processing this line
                           2 - we are on a 'do skipped records pass' and this
                               scanline has been marked to skip yet again
   
   =cut
   
   sub scantron_getfile {
       #FIXME really would prefer a scantron directory
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my $lines;
       $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
          'scantron_orig_'.$env{'form.scantron_selectfile'});
       my %scanlines;
       $scanlines{'orig'}=[(split("\n",$lines,-1))];
       my $temp=$scanlines{'orig'};
       $scanlines{'count'}=$#$temp;
   
       $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
          'scantron_corrected_'.$env{'form.scantron_selectfile'});
       if ($lines eq '-1') {
    $scanlines{'corrected'}=[];
       } else {
    $scanlines{'corrected'}=[(split("\n",$lines,-1))];
       }
       $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
          'scantron_skipped_'.$env{'form.scantron_selectfile'});
       if ($lines eq '-1') {
    $scanlines{'skipped'}=[];
       } else {
    $scanlines{'skipped'}=[(split("\n",$lines,-1))];
       }
       my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
       if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
       my %scan_data = @tmp;
       return (\%scanlines,\%scan_data);
   }
   
   =pod
   
   =item lonnet_putfile
   
      Wrapper routine to call &Apache::lonnet::finishuserfileupload
   
    Arguments:
      $contents - data to store
      $filename - filename to store $contents into
   
    Returns:
      result value from &Apache::lonnet::finishuserfileupload
   
   =cut
   
   sub lonnet_putfile {
       my ($contents,$filename)=@_;
       my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       $env{'form.sillywaytopassafilearound'}=$contents;
       &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
   
   }
   
   =pod
   
   =item scantron_putfile
   
       Stores the current version of the bubble sheet data files, and the
       scan_data hash. (Does not modify the original version only the
       corrected and skipped versions.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
   
   =cut
   
   sub scantron_putfile {
       my ($scanlines,$scan_data) = @_;
       #FIXME really would prefer a scantron directory
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       if ($scanlines) {
    my $prefix='scantron_';
   # no need to update orig, shouldn't change
   #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
   #    $env{'form.scantron_selectfile'});
    &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
    $prefix.'corrected_'.
    $env{'form.scantron_selectfile'});
    &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
    $prefix.'skipped_'.
    $env{'form.scantron_selectfile'});
       }
       &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
   }
   
   =pod
   
   =item scantron_get_line
   
      Returns the correct version of the scanline
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - number of the requested line (starts at 0)
   
    Returns:
      A scanline, (either the original or the corrected one if it
      exists), or undef if the requested scanline should be
      skipped. (Either because it's an skipped scanline, or it's an
      unskipped scanline and we are not doing a 'do skipped scanlines'
      pass.
   
   =cut
   
   sub scantron_get_line {
       my ($scanlines,$scan_data,$i)=@_;
       if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
       #if ($scanlines->{'skipped'}[$i]) { return undef; }
       if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
       return $scanlines->{'orig'}[$i]; 
   }
   
   =pod
   
   =item scantron_todo_count
   
       Counts the number of scanlines that need processing.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
   
    Returns:
       $count - number of scanlines to process
   
   =cut
   
   sub get_todo_count {
       my ($scanlines,$scan_data)=@_;
       my $count=0;
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    $count++;
       }
       return $count;
   }
   
   =pod
   
   =item scantron_put_line
   
       Updates the 'corrected' or 'skipped' versions of the bubble sheet
       data file.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - line number to update
       $newline   - contents of the updated scanline
       $skip      - if true make the line for skipping and update the
                    'skipped' file
   
   =cut
   
   sub scantron_put_line {
       my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
       if ($skip) {
    $scanlines->{'skipped'}[$i]=$newline;
    &start_skipping($scan_data,$i);
    return;
       }
       $scanlines->{'corrected'}[$i]=$newline;
   }
   
   =pod
   
   =item scantron_clear_skip
   
      Remove a line from the 'skipped' file
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - line number to update
   
   =cut
   
   sub scantron_clear_skip {
       my ($scanlines,$scan_data,$i)=@_;
       if (exists($scanlines->{'skipped'}[$i])) {
    undef($scanlines->{'skipped'}[$i]);
    return 1;
       }
       return 0;
   }
   
   =pod
   
   =item scantron_filter_not_exam
   
      Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
      filter out resources that are not marked as 'exam' mode
   
   =cut
   
   sub scantron_filter_not_exam {
       my ($curres)=@_;
       
       if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) {
    # if the user has asked to not have either hidden
    # or 'randomout' controlled resources to be graded
    # don't include them
    if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
       && $curres->randomout) {
       return 0;
    }
    return 1;
       }
       return 0;
   }
   
   =pod
   
   =item scantron_validate_sequence
   
       Validates the selected sequence, checking for resource that are
       not set to exam mode.
   
   =cut
   
   sub scantron_validate_sequence {
       my ($r,$currentphase) = @_;
   
       my $navmap=Apache::lonnavmaps::navmap->new();
       my (undef,undef,$sequence)=
    &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
       my $map=$navmap->getResourceByUrl($sequence);
   
       $r->print('<input type="hidden" name="validate_sequence_exam"
                                       value="ignore" />');
       if ($env{'form.validate_sequence_exam'} ne 'ignore') {
    my @resources=
       $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
    if (@resources) {
       $r->print("<p>".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."</p>");
       return (1,$currentphase);
    }
       }
   
       return (0,$currentphase+1);
   }
   
   =pod
   
   =item scantron_validate_ID
   
      Validates all scanlines in the selected file to not have any
      invalid or underspecified student IDs
   
   =cut
   
   sub scantron_validate_ID {
       my ($r,$currentphase) = @_;
       
       #get student info
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&username_to_idmap($classlist);
   
       #get scantron line setup
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
       
       &scantron_get_maxbubble(); # parse needs the bubble_lines.. array.
   
       my %found=('ids'=>{},'usernames'=>{});
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
    $scan_data);
    my $id=$$scan_record{'scantron.ID'};
    my $found;
    foreach my $checkid (keys(%idmap)) {
       if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
    }
    if ($found) {
       my $username=$idmap{$found};
       if ($found{'ids'}{$found}) {
    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
    $line,'duplicateID',$found);
    return(1,$currentphase);
       } elsif ($found{'usernames'}{$username}) {
    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
    $line,'duplicateID',$username);
    return(1,$currentphase);
       }
       #FIXME store away line we previously saw the ID on to use above
       $found{'ids'}{$found}++;
       $found{'usernames'}{$username}++;
    } else {
       if ($id =~ /^\s*$/) {
    my $username=&scan_data($scan_data,"$i.user");
    if (defined($username) && $found{'usernames'}{$username}) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'duplicateID',$username);
       return(1,$currentphase);
    } elsif (!defined($username)) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'incorrectID');
       return(1,$currentphase);
    }
    $found{'usernames'}{$username}++;
       } else {
    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
    $line,'incorrectID');
    return(1,$currentphase);
       }
    }
       }
   
       return (0,$currentphase+1);
   }
   
   =pod
   
   =item scantron_get_correction
   
      Builds the interface screen to interact with the operator to fix a
      specific error condition in a specific scanline
   
    Arguments:
       $r           - Apache request object
       $i           - number of the current scanline
       $scan_record - hash ref as returned from &scantron_parse_scanline()
       $scan_config - hash ref as returned from &get_scantron_config()
       $line        - full contents of the current scanline
       $error       - error condition, valid values are
                      'incorrectCODE', 'duplicateCODE',
                      'doublebubble', 'missingbubble',
                      'duplicateID', 'incorrectID'
       $arg         - extra information needed
          For errors:
            - duplicateID   - paper number that this studentID was seen before on
            - duplicateCODE - array ref of the paper numbers this CODE was
                              seen on before
            - incorrectCODE - current incorrect CODE 
            - doublebubble  - array ref of the bubble lines that have double
                              bubble errors
            - missingbubble - array ref of the bubble lines that have missing
                              bubble errors
   
   =cut
   
   sub scantron_get_correction {
       my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
   
   #FIXME in the case of a duplicated ID the previous line, probably need
   #to show both the current line and the previous one and allow skipping
   #the previous one or the current one
   
       $r->print("<p><b>An error was detected ($error)</b>");
       if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
    $r->print(" for PaperID <tt>".
     $$scan_record{'scantron.PaperID'}."</tt> \n");
       } else {
    $r->print(" in scanline $i <pre>".
     $line."</pre> \n");
       }
       my $message="<p>The ID on the form is  <tt>".
    $$scan_record{'scantron.ID'}."</tt><br />\n".
    "The name on the paper is ".
    $$scan_record{'scantron.LastName'}.",".
    $$scan_record{'scantron.FirstName'}."</p>";
   
       $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 '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");
    }
    $r->print($message);
    $r->print("<p>How should I handle this? <br /> \n");
    $r->print("\n<ul><li> ");
    #FIXME it would be nice if this sent back the user ID and
    #could do partial userID matches
    $r->print(&Apache::loncommon::selectstudent_link('scantronupload',
          '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'));
   
    $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 ".join(', ',@{$arg}).", and CODEs are 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($message);
    $r->print("<p>How should I handle this? <br /> \n");
    $r->print("\n<br /> ");
    my $i=0;
    if ($error eq 'incorrectCODE' 
       && $$scan_record{'scantron.CODE'}=~/\S/ ) {
       my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
       if ($closest > 0) {
    foreach my $testcode (@{$closest}) {
       my $checked='';
       if (!$i) { $checked=' checked="checked" '; }
       $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++;
    }
       }
    }
    if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
       my $checked; if (!$i) { $checked=' checked="checked" '; }
       $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">
   function change_radio(field) {
       var slct=document.scantronupload.scantron_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=".&escape("scantronupload").
      "&scantron_format=".&escape($env{'form.scantron_format'}).
      "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}).
      "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
      "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
    if ($env{'form.scantron_CODElist'} =~ /\S/) { 
       $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("<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");
    $r->print('<input type="hidden" name="scantron_questions" value="'.
     join(',',@{$arg}).'" />');
    $r->print($message);
    $r->print("<p>Please indicate which bubble should be used for grading</p>");
    foreach my $question (@{$arg}) {
       my $selected  = &get_response_bubbles($scan_record, $question);
       my @select_array = split(/:/,$selected);
       &scantron_bubble_selector($r,$scan_config,$question,
         @select_array);
    }
       } elsif ($error eq 'missingbubble') {
    $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");
    $r->print($message);
    $r->print("<p>Please indicate which bubble should be used for grading</p>");
    $r->print("Some questions have no scanned bubbles\n");
    $r->print('<input type="hidden" name="scantron_questions" value="'.
     join(',',@{$arg}).'" />');
    foreach my $question (@{$arg}) {
       my $selected = &get_response_bubbles($scan_record, $question);
       my @select_array = split(/:/,$selected); # ought to be an array of empties.
       &scantron_bubble_selector($r,$scan_config,$question, @select_array);
    }
       } else {
    $r->print("\n<ul>");
       }
       $r->print("\n</li></ul>");
   
   }
   
   =pod
   
   =item scantron_bubble_selector
     
      Generates the html radiobuttons to correct a single bubble line
      possibly showing the existing the selected bubbles if known
   
    Arguments:
       $r           - Apache request object
       $scan_config - hash from &get_scantron_config()
       $quest       - number of the bubble line to make a corrector for
       @lines       - array of answer lines.
   
   =cut
   
   sub scantron_bubble_selector {
       my ($r,$scan_config,$quest,@lines)=@_;
       my $max=$$scan_config{'Qlength'};
   
   
       my $scmode=$$scan_config{'Qon'};
   
       my $bubble_length = scalar(@lines);
   
   
       if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }     
   
       my $response = $quest-1;
       my $lines = $bubble_lines_per_response{$response};
   
       my $total_lines = $lines*2;
       my @alphabet=('A'..'Z');
   
       $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");
   
       for (my $l = 0; $l < $lines; $l++) {
    if ($l != 0) {
       $r->print('<tr>');
    }
    my @selected = split(//,$lines[$l]);
    for (my $i=0;$i<$max;$i++) {
       $r->print("\n".'<td align="center">');
       if ($selected[0] eq $alphabet[$i]) { 
    $r->print('X'); 
    shift(@selected) ;
       } else { 
    $r->print('&nbsp;'); 
       }
       $r->print('</td>');
       
    }
   
    if ($l == 0) {
       my $lspan = $total_lines * 2;   #  2 table rows per bubble line.
   
       $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'.
         $quest.'" value="none" /> No bubble </label></td>');
   
    }
   
    $r->print('</tr><tr>');
   
    # FIXME: This may have to be a bit more clever for
    #        multiline questions (different values e.g..).
   
    for (my $i=0;$i<$max;$i++) {
       my $value = "$l:$i"; # Relative bubble line #: Bubble in line.
       $r->print("\n".
         '<td><label><input type="radio" name="scantron_correct_Q_'.
         $quest.'" value="'.$value.'" />'.$alphabet[$i]."</label></td>");
    }
    $r->print('</tr>');
   
       
       }
       $r->print('</table>');
   }
   
   =pod
   
   =item num_matches
   
      Counts the number of characters that are the same between the two arguments.
   
    Arguments:
      $orig - CODE from the scanline
      $code - CODE to match against
   
    Returns:
      $count - integer count of the number of same characters between the
               two arguments
   
   =cut
   
   sub num_matches {
       my ($orig,$code) = @_;
       my @code=split(//,$code);
       my @orig=split(//,$orig);
       my $same=0;
       for (my $i=0;$i<scalar(@code);$i++) {
    if ($code[$i] eq $orig[$i]) { $same++; }
       }
       return $same;
   }
   
   =pod
   
   =item scantron_get_closely_matching_CODEs
   
      Cycles through all CODEs and finds the set that has the greatest
      number of same characters as the provided CODE
   
    Arguments:
      $allcodes - hash ref returned by &get_codes()
      $CODE     - CODE from the current scanline
   
    Returns:
      2 element list
       - first elements is number of how closely matching the best fit is 
         (5 means best set has 5 matching characters)
       - second element is an arrary ref containing the set of valid CODEs
         that best fit the passed in CODE
   
   =cut
   
   sub scantron_get_closely_matching_CODEs {
       my ($allcodes,$CODE)=@_;
       my @CODEs;
       foreach my $testcode (sort(keys(%{$allcodes}))) {
    push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode);
       }
   
       return ($#CODEs,$CODEs[-1]);
   }
   
   =pod
   
   =item get_codes
   
      Builds a hash which has keys of all of the valid CODEs from the selected
      set of remembered CODEs.
   
    Arguments:
     $old_name - name of the set of remembered CODEs
     $cdom     - domain of the course
     $cnum     - internal course name
   
    Returns:
     %allcodes - keys are the valid CODEs, values are all 1
   
   =cut
   
   sub get_codes {
       my ($old_name, $cdom, $cnum) = @_;
       if (!$old_name) {
    $old_name=$env{'form.scantron_CODElist'};
       }
       if (!$cdom) {
    $cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
       }
       if (!$cnum) {
    $cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
       }
       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;
   }
   
   =pod
   
   =item scantron_validate_CODE
   
      Validates all scanlines in the selected file to not have any
      invalid or underspecified CODEs and that none of the codes are
      duplicated if this was requested.
   
   =cut
   
   sub scantron_validate_CODE {
       my ($r,$currentphase) = @_;
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       if ($scantron_config{'CODElocation'} &&
    $scantron_config{'CODEstart'} &&
    $scantron_config{'CODElength'}) {
    if (!defined($env{'form.scantron_CODElist'})) {
       &FIXME_blow_up()
    }
       } else {
    return (0,$currentphase+1);
       }
       
       my %usedCODEs;
   
       my %allcodes=&get_codes();
   
       &scantron_get_maxbubble(); # parse needs the lines per response array.
   
       my ($scanlines,$scan_data)=&scantron_getfile();
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    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 (!&Apache::lonnet::validCODE($CODE)) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'incorrectCODE',\%allcodes);
       return(1,$currentphase);
    }
    if (%allcodes && !exists($allcodes{$CODE}) 
       && !$$scan_record{'scantron.useCODE'}) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'incorrectCODE',\%allcodes);
       return(1,$currentphase);
    }
    if (exists($usedCODEs{$CODE}) 
       && $env{'form.scantron_CODEunique'} eq 'yes'
       && !$$scan_record{'scantron.CODE_ignore_dup'}) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'duplicateCODE',$usedCODEs{$CODE});
       return(1,$currentphase);
    }
    push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
       }
       return (0,$currentphase+1);
   }
   
   =pod
   
   =item scantron_validate_doublebubble
   
      Validates all scanlines in the selected file to not have any
      bubble lines with multiple bubbles marked.
   
   =cut
   
   sub scantron_validate_doublebubble {
       my ($r,$currentphase) = @_;
       #get student info
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&username_to_idmap($classlist);
   
       #get scantron line setup
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
   
       &scantron_get_maxbubble(); # parse needs the bubble line array.
   
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
    $scan_data);
    if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
    'doublebubble',
    $$scan_record{'scantron.doubleerror'});
       return (1,$currentphase);
       }
       return (0,$currentphase+1);
   }
   
   =pod
   
   =item scantron_get_maxbubble
   
      Returns the maximum number of bubble lines that are expected to
      occur. Does this by walking the selected sequence rendering the
      resource and then checking &Apache::lonxml::get_problem_counter()
      for what the current value of the problem counter is.
   
      Caches the results to $env{'form.scantron_maxbubble'},
      $env{'form.scantron.bubble_lines.n'} and 
      $env{'form.scantron.first_bubble_line.n'}
      which are the total number of bubble, lines, the number of bubble
      lines for reponse n and number of the first bubble line for response n.
   
   =cut
   
   sub scantron_get_maxbubble {    
       if (defined($env{'form.scantron_maxbubble'}) &&
    $env{'form.scantron_maxbubble'}) {
    &restore_bubble_lines();
    return $env{'form.scantron_maxbubble'};
       }
   
       my (undef, undef, $sequence) =
    &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
       my $navmap=Apache::lonnavmaps::navmap->new();
       my $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
   
       &Apache::lonxml::clear_problem_counter();
   
       my $uname       = $env{'form.student'};
       my $udom        = $env{'form.userdom'};
       my $cid         = $env{'request.course.id'};
       my $total_lines = 0;
       %bubble_lines_per_response = ();
       %first_bubble_line         = ();
   
     
       my $response_number = 0;
       my $bubble_line     = 0;
       foreach my $resource (@resources) {
    my $symb = $resource->symb();
    &Apache::lonxml::clear_bubble_lines_for_part();
    my $result=&Apache::lonnet::ssi($resource->src(),
    ('symb' => $resource->symb()),
    ('grade_target' => 'analyze'),
    ('grade_courseid' => $cid),
    ('grade_domain' => $udom),
    ('grade_username' => $uname));
    my (undef, $an) =
       split(/_HASH_REF__/,$result, 2);
   
    my %analysis = &Apache::lonnet::str2hash($an);
   
   
   
    foreach my $part_id (@{$analysis{'parts'}}) {
   
   
       my $lines = $analysis{"$part_id.bubble_lines"};;
   
       # TODO - make this a persistent hash not an array.
   
   
       $first_bubble_line{$response_number}           = $bubble_line;
       $bubble_lines_per_response{$response_number}   = $lines;
       $response_number++;
   
       $bubble_line +=  $lines;
       $total_lines +=  $lines;
    }
   
       }
       &Apache::lonnet::delenv('scantron\.');
   
       &save_bubble_lines();
       $env{'form.scantron_maxbubble'} =
    $total_lines;
       return $env{'form.scantron_maxbubble'};
   }
   
   =pod
   
   =item scantron_validate_missingbubbles
   
      Validates all scanlines in the selected file to not have any
       answers that don't have bubbles that have not been verified
       to be bubble free.
   
   =cut
   
   sub scantron_validate_missingbubbles {
       my ($r,$currentphase) = @_;
       #get student info
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&username_to_idmap($classlist);
   
       #get scantron line setup
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
       my $max_bubble=&scantron_get_maxbubble();
       if (!$max_bubble) { $max_bubble=2**31; }
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
    $scan_data);
    if (!defined($$scan_record{'scantron.missingerror'})) { next; }
    my @to_correct;
   
    # Probably here's where the error is...
   
    foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
       if ($missing > $max_bubble) { next; }
       push(@to_correct,$missing);
    }
    if (@to_correct) {
       &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
        $line,'missingbubble',\@to_correct);
       return (1,$currentphase);
    }
   
       }
       return (0,$currentphase+1);
   }
   
   =pod
   
   =item scantron_process_students
   
      Routine that does the actual grading of the bubble sheet information.
   
      The parsed scanline hash is added to %env 
   
      Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
      foreach resource , with the form data of
   
    'submitted'     =>'scantron' 
    'grade_target'  =>'grade',
    'grade_username'=> username of student
    'grade_domain'  => domain of student
    'grade_courseid'=> of course
    'grade_symb'    => symb of resource to grade
   
       This triggers a grading pass. The problem grading code takes care
       of converting the bubbled letter information (now in %env) into a
       valid submission.
   
   =cut
   
   sub scantron_process_students {
       my ($r) = @_;
       my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $default_form_data=&defaultFormData($symb);
   
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&username_to_idmap($classlist);
       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 $result= <<SCANTRONFORM;
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
     <input type="hidden" name="command" value="scantron_configphase" />
     $default_form_data
   SCANTRONFORM
       $r->print($result);
   
       my @delayqueue;
       my %completedstudents;
       
       my $count=&get_todo_count($scanlines,$scan_data);
       my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',
        'Scantron Progress',$count,
       'inline',undef,'scantronupload');
       &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
     'Processing first student');
       my $start=&Time::HiRes::time();
       my $i=-1;
       my ($uname,$udom,$started);
   
       &scantron_get_maxbubble(); # Need the bubble lines array to parse.
   
       while ($i<$scanlines->{'count'}) {
     ($uname,$udom)=('','');
     $i++;
     my $line=&scantron_get_line($scanlines,$scan_data,$i);
     if ($line=~/^[\s\cz]*$/) { next; }
    if ($started) {
       &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
        'last student');
    }
    $started=1;
     my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
     $scan_data);
     unless ($uname=&scantron_find_student($scan_record,$scan_data,
          \%idmap,$i)) {
        &scantron_add_delay(\@delayqueue,$line,
     'Unable to find a student that matches',1);
        next;
     }
     if (exists $completedstudents{$uname}) {
        &scantron_add_delay(\@delayqueue,$line,
     'Student '.$uname.' has multiple sheets',2);
        next;
     }
     ($uname,$udom)=split(/:/,$uname);
   
    &Apache::lonxml::clear_problem_counter();
     &Apache::lonnet::appenv(%$scan_record);
   
    if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
       &scantron_putfile($scanlines,$scan_data);
    }
   
    my $i=0;
    foreach my $resource (@resources) {
       $i++;
       my %form=('submitted'     =>'scantron',
         'grade_target'  =>'grade',
         'grade_username'=>$uname,
         'grade_domain'  =>$udom,
         'grade_courseid'=>$env{'request.course.id'},
         'grade_symb'    =>$resource->symb());
       if (exists($scan_record->{'scantron.CODE'})
    && 
    &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {
    $form{'CODE'}=$scan_record->{'scantron.CODE'};
       } else {
    $form{'CODE'}='';
       }
       my $result=&Apache::lonnet::ssi($resource->src(),%form);
       if ($result ne '') {
       }
       if (&Apache::loncommon::connection_aborted($r)) { last; }
    }
    $completedstudents{$uname}={'line'=>$line};
    if (&Apache::loncommon::connection_aborted($r)) { last; }
       } continue {
    &Apache::lonxml::clear_problem_counter();
    &Apache::lonnet::delenv('scantron\.');
       }
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
   #    my $lasttime = &Time::HiRes::time()-$start;
   #    $r->print("<p>took $lasttime</p>");
   
       $r->print("</form>");
       $r->print(&show_grading_menu_form($symb));
       return '';
   }
   
   =pod
   
   =item scantron_upload_scantron_data
   
       Creates the screen for adding a new bubble sheet data file to a course.
   
   =cut
   
   sub scantron_upload_scantron_data {
       my ($r)=@_;
       $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
       my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
     'domainid',
     'coursename');
       my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},
      'domainid');
       my $default_form_data=&defaultFormData(&get_symb($r,1));
       $r->print(<<UPLOAD);
   <script type="text/javascript" language="javascript">
       function checkUpload(formname) {
    if (formname.upfile.value == "") {
       alert("Please use the browse button to select a file from your local directory.");
       return false;
    }
    formname.submit();
       }
   </script>
   
   <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
   $default_form_data
   <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' />
   <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />
   </form>
   UPLOAD
       return '';
   }
   
   =pod
   
   =item scantron_upload_scantron_data_save
   
      Adds a provided bubble information data file to the course if user
      has the correct privileges to do so.  
   
   =cut
   
   sub scantron_upload_scantron_data_save {
       my($r)=@_;
       my ($symb)=&get_symb($r,1);
       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 />");
    if ($symb) {
       $r->print(&show_grading_menu_form($symb));
    } else {
       $r->print($doanotherupload);
    }
    return '';
       }
       my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
       $r->print("Doing upload to ".$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;
       if (length($env{'form.upfile'}) < 2) {
    $r->print("<span class=\"LC_error\">Error:</span> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.");
       } else {
    my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);
    if ($result =~ m|^/uploaded/|) {
       $r->print("<span class=\"LC_success\">Success:</span> Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");
    } else {
       $r->print("<span class=\"LC_error\">Error:</span> An error (".$result.") occurred when attempting to upload the file, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>");
    }
       }
       if ($symb) {
    $r->print(&scantron_selectphase($r,$uploadedfile));
       } else {
    $r->print($doanotherupload);
       }
       return '';
   }
   
   =pod
   
   =item valid_file
   
      Validates that the requested bubble data file exists in the course.
   
   =cut
   
   sub valid_file {
       my ($requested_file)=@_;
       foreach my $filename (sort(&scantron_filenames())) {
    if ($requested_file eq $filename) { return 1; }
       }
       return 0;
   }
   
   =pod
   
   =item scantron_download_scantron_data
   
      Shows a list of the three internal files (original, corrected,
      skipped) for a specific bubble sheet data file that exists in the
      course.
   
   =cut
   
   sub scantron_download_scantron_data {
       my ($r)=@_;
       my $default_form_data=&defaultFormData(&get_symb($r,1));
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my $file=$env{'form.scantron_selectfile'};
       if (! &valid_file($file)) {
    $r->print(<<ERROR);
    <p>
       The requested file name was invalid.
           </p>
   ERROR
    $r->print(&show_grading_menu_form(&get_symb($r,1)));
    return;
       }
       my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
       my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
       my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
       &Apache::lonnet::allowuploaded('/adm/grades',$orig);
       &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
       &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
       $r->print(<<DOWNLOAD);
       <p>
    <a href="$orig">Original</a> file as uploaded by the scantron office.
       </p>
       <p>
    <a href="$corrected">Corrections</a>, a file of corrected records that were used in grading.
       </p>
       <p>
    <a href="$skipped">Skipped</a>, a file of records that were skipped.
       </p>
   DOWNLOAD
       $r->print(&show_grading_menu_form(&get_symb($r,1)));
       return '';
   }
   
   =pod
   
   =back
   
   =cut
   
   #-------- end of section for handling grading scantron forms -------
   #
   #-------------------------------------------------------------------
   
   #-------------------------- Menu interface -------------------------
   #
   #--- Show a Grading Menu button - Calls the next routine ---
   sub show_grading_menu_form {
       my ($symb)=@_;
       my $result.='<br /><form action="/adm/grades" method="post">'."\n".
    '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
    '<input type="hidden" name="command" value="gradingmenu" />'."\n".
    '<input type="submit" name="submit" value="'.&mt('Grading Menu').'" />'."\n".
    '</form>'."\n";
       return $result;
   }
   
   # -- Retrieve choices for grading form
   sub savedState {
       my %savedState = ();
       if ($env{'form.saveState'}) {
    foreach (split(/:/,$env{'form.saveState'})) {
       my ($key,$value) = split(/=/,$_,2);
       $savedState{$key} = $value;
    }
       }
       return \%savedState;
   }
   
   sub grading_menu {
       my ($request) = @_;
       my ($symb)=&get_symb($request);
       if (!$symb) {return '';}
       my $probTitle = &Apache::lonnet::gettitle($symb);
       my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
   
       $request->print($table);
       my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
                     'handgrade'=>$hdgrade,
                     'probTitle'=>$probTitle,
                     'command'=>'submit_options',
                     'saveState'=>"",
                     'gradingMenu'=>1,
                     'showgrading'=>"yes");
       my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       my @menu = ({ url => $url,
                        name => &mt('Manual Grading/View Submissions'),
                        short_description => 
       &mt('Start the process of hand grading submissions.'),
                    });
       $fields{'command'} = 'csvform';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Upload Scores'),
                      short_description => 
               &mt('Specify a file containing the class scores for current resource.')});
       $fields{'command'} = 'processclicker';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Process Clicker'),
                      short_description => 
               &mt('Specify a file containing the clicker information for this resource.')});
       $fields{'command'} = 'scantron_selectphase';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Grade/Manage Scantron Forms'),
                      short_description => 
               &mt('')});
       $fields{'command'} = 'verify';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => "",
                      name => &mt('Verify Receipt'),
                      short_description => 
               &mt('')});
       #
       # Create the menu
       my $Str;
       # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>';
       $Str .= '<form method="post" action="" name="gradingMenu">';
       $Str .= '<input type="hidden" name="command" value="" />'.
       '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
    '<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
    '<input type="hidden" name="saveState"   value="" />'."\n".
    '<input type="hidden" name="gradingMenu" value="1" />'."\n".
    '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
       foreach my $menudata (@menu) {
           if ($menudata->{'name'} ne &mt('Verify Receipt')) {
               $Str .='    <h3><a '.
                   $menudata->{'jscript'}.
                   ' href="'.
                   $menudata->{'url'}.'" >'.
                   $menudata->{'name'}."</a></h3>\n";
           } else {
               $Str .='    <h3><input type="button" value="'.&mt('Verify Receipt').'" '.
                   $menudata->{'jscript'}.
                   ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
                   ' /></h3>';
               $Str .= ('&nbsp;'x8).
    &mt(' receipt: [_1]',
       &Apache::lonnet::recprefix($env{'request.course.id'}).
                       '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />');
           }
           $Str .= '    '.('&nbsp;'x8).$menudata->{'short_description'}.
               "\n";
       }
       $Str .="</form>\n";
       $request->print(<<GRADINGMENUJS);
   <script type="text/javascript" language="javascript">
       function checkChoice(formname,val,cmdx) {
    if (val <= 2) {
       var cmd = radioSelection(formname.radioChoice);
       var cmdsave = cmd;
    } else {
       cmd = cmdx;
       cmdsave = 'submission';
    }
    formname.command.value = cmd;
    if (val < 5) formname.submit();
    if (val == 5) {
       if (!checkReceiptNo(formname,'notOK')) { 
           return false;
       } else {
           formname.submit();
       }
    }
       }
   
       function checkReceiptNo(formname,nospace) {
    var receiptNo = formname.receipt.value;
    var checkOpt = false;
    if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
    if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
    if (checkOpt) {
       alert("Please enter a receipt number given by a student in the receipt box.");
       formname.receipt.value = "";
       formname.receipt.focus();
       return false;
    }
    return true;
       }
   </script>
   GRADINGMENUJS
       &commonJSfunctions($request);
       return $Str;    
   }
   
   
   #--- Displays the submissions first page -------
   sub submit_options {
       my ($request) = @_;
       my ($symb)=&get_symb($request);
       if (!$symb) {return '';}
       my $probTitle = &Apache::lonnet::gettitle($symb);
   
       $request->print(<<GRADINGMENUJS);
   <script type="text/javascript" language="javascript">
       function checkChoice(formname,val,cmdx) {
    if (val <= 2) {
       var cmd = radioSelection(formname.radioChoice);
       var cmdsave = cmd;
    } else {
       cmd = cmdx;
       cmdsave = 'submission';
    }
    formname.command.value = cmd;
    formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
       ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
    if (val < 5) formname.submit();
    if (val == 5) {
       if (!checkReceiptNo(formname,'notOK')) { return false;}
       formname.submit();
    }
    if (val < 7) formname.submit();
       }
   
       function checkReceiptNo(formname,nospace) {
    var receiptNo = formname.receipt.value;
    var checkOpt = false;
    if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
    if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
    if (checkOpt) {
       alert("Please enter a receipt number given by a student in the receipt box.");
       formname.receipt.value = "";
       formname.receipt.focus();
       return false;
    }
    return true;
       }
   </script>
   GRADINGMENUJS
       &commonJSfunctions($request);
       my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
       my $result;
       my (undef,$sections) = &getclasslist('all','0');
       my $savedState = &savedState();
       my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});
       my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'});
       my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});
       my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
   
       $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
    '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
    '<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
    '<input type="hidden" name="command"     value="" />'."\n".
    '<input type="hidden" name="saveState"   value="" />'."\n".
    '<input type="hidden" name="gradingMenu" value="1" />'."\n".
    '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
       $result.='
       <div class="LC_grade_select_mode">
         <div class="LC_grade_select_mode_current">
           <h2>
             '.&mt('Grade Current Resource').'
           </h2>
           <div class="LC_grade_select_mode_body">
             <div class="LC_grades_resource_info">
              '.$table.'
             </div>
             <div class="LC_grade_select_mode_selector">
                <div class="LC_grade_select_mode_selector_header">
                   '.&mt('Sections').'
                </div>
                <div class="LC_grade_select_mode_selector_body">
          <select name="section" multiple="multiple" size="5">'."\n";
       if (ref($sections)) {
    foreach my $section (sort (@$sections)) {
       $result.='<option value="'.$section.'" '.
    ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";
    }
       }
       $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
       $result.='
                </div>
             </div>
             <div class="LC_grade_select_mode_selector">
                <div class="LC_grade_select_mode_selector_header">
                   '.&mt('Groups').'
                </div>
                <div class="LC_grade_select_mode_selector_body">
                   '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
                </div>
             </div>
             <div class="LC_grade_select_mode_selector">
                <div class="LC_grade_select_mode_selector_header">
                   '.&mt('Access Status').'
                </div>
                <div class="LC_grade_select_mode_selector_body">
                   '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'
                </div>
             </div>
             <div class="LC_grade_select_mode_selector">
                <div class="LC_grade_select_mode_selector_header">
                   '.&mt('Submission Status').'
                </div>
                <div class="LC_grade_select_mode_selector_body">
                  <select name="submitonly" size="5">
            <option value="yes" '.      ($saveSub eq 'yes'       ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>
            <option value="queued" '.   ($saveSub eq 'queued'    ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>
            <option value="graded" '.   ($saveSub eq 'graded'    ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>
            <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>
                    <option value="all" '.      ($saveSub eq 'all'       ? 'selected="selected"' : '').'>'.&mt('with any status').'</option>
                  </select>
                </div>
             </div>
             <div class="LC_grade_select_mode_type_body">
               <div class="LC_grade_select_mode_type">
                 <label>
                   <input type="radio" name="radioChoice" value="submission" '.
                     ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.
                &mt('Select individual students to grade and view submissions.').'
         </label> 
               </div>
               <div class="LC_grade_select_mode_type">
         <label>
                   <input type="radio" name="radioChoice" value="viewgrades" '.
                     ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
                       &mt('Grade all selected students in a grading table.').'
                 </label>
               </div>
               <div class="LC_grade_select_mode_type">
         <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next-&gt;').'" />
               </div>
             </div>
           </div>
         </div>
         <div class="LC_grade_select_mode_page">
           <h2>
            '.&mt('Grade Complete Folder for One Student').'
           </h2>
           <div class="LC_grades_select_mode_body">
             <div class="LC_grade_select_mode_type_body">
               <div class="LC_grade_select_mode_type">
                 <label>
                   <input type="radio" name="radioChoice" value="pickStudentPage" '.
     ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
     &mt('The <b>complete</b> page/sequence/folder: For one student').'
                 </label>
               </div>
               <div class="LC_grade_select_mode_type">
         <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next-&gt;').'" />
               </div>
             </div>
           </div>
         </div>
       </div>
     </form>';
       return $result;
   }
   
   sub reset_perm {
       undef(%perm);
   }
   
   sub init_perm {
       &reset_perm();
       foreach my $test_perm ('vgr','mgr','opa') {
   
    my $scope = $env{'request.course.id'};
    if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) {
   
       $scope .= '/'.$env{'request.course.sec'};
       if ( $perm{$test_perm}=
    &Apache::lonnet::allowed($test_perm,$scope)) {
    $perm{$test_perm.'_section'}=$env{'request.course.sec'};
       } else {
    delete($perm{$test_perm});
       }
    }
       }
   }
   
   sub gather_clicker_ids {
       my %clicker_ids;
   
       my $classlist = &Apache::loncoursedata::get_classlist();
   
       # 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 =
       (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
           foreach my $id (split(/\,/,$clickers)) {
               $id=~s/^[\#0]+//;
               $id=~s/[\-\:]//g;
               if (exists($clicker_ids{$id})) {
    $clicker_ids{$id}.=','.$username.':'.$domain;
               } else {
    $clicker_ids{$id}=$username.':'.$domain;
               }
           }
       }
       return %clicker_ids;
   }
   
   sub gather_adv_clicker_ids {
       my %clicker_ids;
       my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
       foreach my $element (sort(keys(%coursepersonnel))) {
           foreach my $person (split(/\,/,$coursepersonnel{$element})) {
               my ($puname,$pudom)=split(/\:/,$person);
               my $clickers =
    (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
               foreach my $id (split(/\,/,$clickers)) {
    $id=~s/^[\#0]+//;
                   $id=~s/[\-\:]//g;
    if (exists($clicker_ids{$id})) {
       $clicker_ids{$id}.=','.$puname.':'.$pudom;
    } else {
       $clicker_ids{$id}=$puname.':'.$pudom;
    }
               }
           }
       }
       return %clicker_ids;
   }
   
   sub clicker_grading_parameters {
       return ('gradingmechanism' => 'scalar',
               'upfiletype' => 'scalar',
               'specificid' => 'scalar',
               'pcorrect' => 'scalar',
               'pincorrect' => 'scalar');
   }
   
   sub process_clicker {
       my ($r)=@_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $result=&checkforfile_js();
       $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
       my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
       $result.=$table;
       $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
       $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
       $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource').
           '.</b></td></tr>'."\n";
       $result.='<tr bgcolor=#ffffe6><td>'."\n";
   # Attempt to restore parameters from last session, set defaults if not present
       my %Saveable_Parameters=&clicker_grading_parameters();
       &Apache::loncommon::restore_course_settings('grades_clicker',
                                                    \%Saveable_Parameters);
       if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
       if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
       if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
       if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
   
       my %checked;
       foreach my $gradingmechanism ('attendance','personnel','specific') {
          if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
             $checked{$gradingmechanism}="checked='checked'";
          }
       }
   
       my $upload=&mt("Upload File");
       my $type=&mt("Type");
       my $attendance=&mt("Award points just for participation");
       my $personnel=&mt("Correctness determined from response by course personnel");
       my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
       my $pcorrect=&mt("Percentage points for correct solution");
       my $pincorrect=&mt("Percentage points for incorrect solution");
       my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
      ('iclicker' => 'i>clicker',
                                                       'interwrite' => 'interwrite PRS'));
       $symb = &Apache::lonenc::check_encrypt($symb);
       $result.=<<ENDUPFORM;
   <script type="text/javascript">
   function sanitycheck() {
   // Accept only integer percentages
      document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
      document.forms.gradesupload.pincorrect.value=Math.round(document.forms.gradesupload.pincorrect.value);
   // Find out grading choice
      for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
         if (document.forms.gradesupload.gradingmechanism[i].checked) {
            gradingchoice=document.forms.gradesupload.gradingmechanism[i].value;
         }
      }
   // By default, new choice equals user selection
      newgradingchoice=gradingchoice;
   // Not good to give more points for false answers than correct ones
      if (Math.round(document.forms.gradesupload.pcorrect.value)<Math.round(document.forms.gradesupload.pincorrect.value)) {
         document.forms.gradesupload.pcorrect.value=document.forms.gradesupload.pincorrect.value;
      }
   // If new choice is attendance only, and old choice was correctness-based, restore defaults
      if ((gradingchoice=='attendance') && (document.forms.gradesupload.waschecked.value!='attendance')) {
         document.forms.gradesupload.pcorrect.value=100;
         document.forms.gradesupload.pincorrect.value=100;
      }
   // If the values are different, cannot be attendance only
      if ((Math.round(document.forms.gradesupload.pcorrect.value)!=Math.round(document.forms.gradesupload.pincorrect.value)) &&
          (gradingchoice=='attendance')) {
          newgradingchoice='personnel';
      }
   // Change grading choice to new one
      for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
         if (document.forms.gradesupload.gradingmechanism[i].value==newgradingchoice) {
            document.forms.gradesupload.gradingmechanism[i].checked=true;
         } else {
            document.forms.gradesupload.gradingmechanism[i].checked=false;
       }        }
      }
   // Remember the old state
      document.forms.gradesupload.waschecked.value=newgradingchoice;
   }
   </script>
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
   <input type="hidden" name="symb" value="$symb" />
   <input type="hidden" name="command" value="processclickerfile" />
   <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
   <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>
   <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
   <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
   <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>
   <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>
   <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
   </form>
   ENDUPFORM
       $result.='</td></tr></table>'."\n".
                '</td></tr></table><br /><br />'."\n";
       $result.=&show_grading_menu_form($symb);
       return $result;
   }
   
   sub process_clicker_file {
       my ($r)=@_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
   
       my %Saveable_Parameters=&clicker_grading_parameters();
       &Apache::loncommon::store_course_settings('grades_clicker',
                                                 \%Saveable_Parameters);
   
       my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
       if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
    $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
    return $result.&show_grading_menu_form($symb);
       }
       my %clicker_ids=&gather_clicker_ids();
       my %correct_ids;
       if ($env{'form.gradingmechanism'} eq 'personnel') {
    %correct_ids=&gather_adv_clicker_ids();
       }
       if ($env{'form.gradingmechanism'} eq 'specific') {
    foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
      $correct_id=~tr/a-z/A-Z/;
      $correct_id=~s/\s//gs;
      $correct_id=~s/^[\#0]+//;
              $correct_id=~s/[\-\:]//g;
              if ($correct_id) {
         $correct_ids{$correct_id}='specified';
              }
           }
       }
       if ($env{'form.gradingmechanism'} eq 'attendance') {
    $result.=&mt('Score based on attendance only');
       } else {
    my $number=0;
    $result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
    foreach my $id (sort(keys(%correct_ids))) {
       $result.='<br /><tt>'.$id.'</tt> - ';
       if ($correct_ids{$id} eq 'specified') {
    $result.=&mt('specified');
       } else {
    my ($uname,$udom)=split(/\:/,$correct_ids{$id});
    $result.=&Apache::loncommon::plainname($uname,$udom);
       }
       $number++;
    }
           $result.="</p>\n";
    if ($number==0) {
       $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
       return $result.&show_grading_menu_form($symb);
    }
       }
       if (length($env{'form.upfile'}) < 2) {
           $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.',
        '<span class="LC_error">',
        '</span>',
        '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');
           return $result.&show_grading_menu_form($symb);
       }
   
   # Were able to get all the info needed, now analyze the file
   
       $result.=&Apache::loncommon::studentbrowser_javascript();
       $symb = &Apache::lonenc::check_encrypt($symb);
       my $heading=&mt('Scanning clicker file');
       $result.=(<<ENDHEADER);
   <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
   <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
   <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
   <form method="post" action="/adm/grades" name="clickeranalysis">
   <input type="hidden" name="symb" value="$symb" />
   <input type="hidden" name="command" value="assignclickergrades" />
   <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
   <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
   <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
   <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
   <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
   ENDHEADER
       my %responses;
       my @questiontitles;
       my $errormsg='';
       my $number=0;
       if ($env{'form.upfiletype'} eq 'iclicker') {
    ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
       }
       if ($env{'form.upfiletype'} eq 'interwrite') {
           ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
       }
       $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
                '<input type="hidden" name="number" value="'.$number.'" />'.
                &mt('Awarding [_1] percent for corrion(s)',$number).'<br />'.
                '<input type="hidden" name="number" value="'.$number.'" />'.
                &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
                    $env{'form.pcorrect'},$env{'form.pincorrect'}).
                '<br />';
   # Remember Question Titles
   # FIXME: Possibly need delimiter other than ":"
       for (my $i=0;$i<$number;$i++) {
           $result.='<input type="hidden" name="question:'.$i.'" value="'.
                    &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
       }
       my $correct_count=0;
       my $student_count=0;
       my $unknown_count=0;
   # Match answers with usernames
   # FIXME: Possibly need delimiter other than ":"
       foreach my $id (keys(%responses)) {
          if ($correct_ids{$id}) {
             $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
             $correct_count++;
          } elsif ($clicker_ids{$id}) {
             if ($clicker_ids{$id}=~/\,/) {
   # More than one user with the same clicker!
                $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
                $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                              "<select name='multi".$id."'>";
                foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
                    $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
                }
                $result.='</select>';
                $unknown_count++;
             } else {
   # Good: found one and only one user with the right clicker
                $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
                $student_count++;
             }
          } else {
             $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
             $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                      "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
                      "\n".&mt("Domain").": ".
                      &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
                      &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);
             $unknown_count++;
          }
       }
       $result.='<hr />'.
                &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
       if ($env{'form.gradingmechanism'} ne 'attendance') {
          if ($correct_count==0) {
             $errormsg.="Found no correct answers answers for grading!";
          } elsif ($correct_count>1) {
             $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
          }
       }
       if ($number<1) {
          $errormsg.="Found no questions.";
       }
       if ($errormsg) {
          $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
     } else {      } else {
       $request->print("Unknown action: $command:");         $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
     }      }
   }      $result.='</form></td></tr></table>'."\n".
   &send_footer($request);               '</td></tr></table><br /><br />'."\n";
   return OK;      return $result.&show_grading_menu_form($symb);
   }
   
   sub iclicker_eval {
       my ($questiontitles,$responses)=@_;
       my $number=0;
       my $errormsg='';
       foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
           my %components=&Apache::loncommon::record_sep($line);
           my @entries=map {$components{$_}} (sort(keys(%components)));
    if ($entries[0] eq 'Question') {
       for (my $i=3;$i<$#entries;$i+=6) {
    $$questiontitles[$number]=$entries[$i];
    $number++;
       }
    }
    if ($entries[0]=~/^\#/) {
       my $id=$entries[0];
       my @idresponses;
       $id=~s/^[\#0]+//;
       for (my $i=0;$i<$number;$i++) {
    my $idx=3+$i*6;
    push(@idresponses,$entries[$idx]);
       }
       $$responses{$id}=join(',',@idresponses);
    }
       }
       return ($errormsg,$number);
   }
   
   sub interwrite_eval {
       my ($questiontitles,$responses)=@_;
       my $number=0;
       my $errormsg='';
       my $skipline=1;
       my $questionnumber=0;
       my %idresponses=();
       foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
           my %components=&Apache::loncommon::record_sep($line);
           my @entries=map {$components{$_}} (sort(keys(%components)));
           if ($entries[1] eq 'Time') { $skipline=0; next; }
           if ($entries[1] eq 'Response') { $skipline=1; }
           next if $skipline;
           if ($entries[0]!=$questionnumber) {
              $questionnumber=$entries[0];
              $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
              $number++;
           }
           my $id=$entries[4];
           $id=~s/^[\#0]+//;
           $id=~s/^v\d*\://i;
           $id=~s/[\-\:]//g;
           $idresponses{$id}[$number]=$entries[6];
       }
       foreach my $id (keys %idresponses) {
          $$responses{$id}=join(',',@{$idresponses{$id}});
          $$responses{$id}=~s/^\s*\,//;
       }
       return ($errormsg,$number);
   }
   
   sub assign_clicker_grades {
       my ($r)=@_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
   # See which part we are saving to
       my ($partlist,$handgrade,$responseType) = &response_type($symb);
   # FIXME: This should probably look for the first handgradeable part
       my $part=$$partlist[0];
   # Start screen output
       my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
   
       my $heading=&mt('Assigning grades based on clicker file');
       $result.=(<<ENDHEADER);
   <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
   <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
   <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
   ENDHEADER
   # Get correct result
   # FIXME: Possibly need delimiter other than ":"
       my @correct=();
       my $gradingmechanism=$env{'form.gradingmechanism'};
       my $number=$env{'form.number'};
       if ($gradingmechanism ne 'attendance') {
          foreach my $key (keys(%env)) {
             if ($key=~/^form\.correct\:/) {
                my @input=split(/\,/,$env{$key});
                for (my $i=0;$i<=$#input;$i++) {
                    if (($correct[$i]) && ($input[$i]) &&
                        ($correct[$i] ne $input[$i])) {
                       $result.='<br /><span class="LC_warning">'.
                                &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
                                    $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
                    } elsif ($input[$i]) {
                       $correct[$i]=$input[$i];
                    }
                }
             }
          }
          for (my $i=0;$i<$number;$i++) {
             if (!$correct[$i]) {
                $result.='<br /><span class="LC_error">'.
                         &mt('No correct result given for question "[_1]"!',
                             $env{'form.question:'.$i}).'</span>';
             }
          }
          $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));
       }
   # Start grading
       my $pcorrect=$env{'form.pcorrect'};
       my $pincorrect=$env{'form.pincorrect'};
       my $storecount=0;
       foreach my $key (keys(%env)) {
          my $user='';
          if ($key=~/^form\.student\:(.*)$/) {
             $user=$1;
          }
          if ($key=~/^form\.unknown\:(.*)$/) {
             my $id=$1;
             if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
                $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
             } elsif ($env{'form.multi'.$id}) {
                $user=$env{'form.multi'.$id};
             }
          }
          if ($user) { 
             my @answer=split(/\,/,$env{$key});
             my $sum=0;
             for (my $i=0;$i<$number;$i++) {
                if ($answer[$i]) {
                   if ($gradingmechanism eq 'attendance') {
                      $sum+=$pcorrect;
                   } else {
                      if ($answer[$i] eq $correct[$i]) {
                         $sum+=$pcorrect;
                      } else {
                         $sum+=$pincorrect;
                      }
                   }
                }
             }
             my $ave=$sum/(100*$number);
   # Store
             my ($username,$domain)=split(/\:/,$user);
             my %grades=();
             $grades{"resource.$part.solved"}='correct_by_override';
             $grades{"resource.$part.awarded"}=$ave;
             $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
             my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
                                                    $env{'request.course.id'},
                                                    $domain,$username);
             if ($returncode ne 'ok') {
                $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
             } else {
                $storecount++;
             }
          }
       }
   # We are done
       $result.='<br />'.&mt('Successfully stored grades for [_1] student(s).',$storecount).
                '</td></tr></table>'."\n".
                '</td></tr></table><br /><br />'."\n";
       return $result.&show_grading_menu_form($symb);
   }
   
   sub handler {
       my $request=$_[0];
       &reset_caches();
       if ($env{'browser.mathml'}) {
    &Apache::loncommon::content_type($request,'text/xml');
       } else {
    &Apache::loncommon::content_type($request,'text/html');
       }
       $request->send_http_header;
       return '' if $request->header_only;
       &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
       my $symb=&get_symb($request,1);
       my @commands=&Apache::loncommon::get_env_multiple('form.command');
       my $command=$commands[0];
   
       if ($#commands > 0) {
    &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
       }
   
   
       $request->print(&Apache::loncommon::start_page('Grading'));
       if ($symb eq '' && $command eq '') {
    if ($env{'user.adv'}) {
       if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&
    ($env{'form.codethree'})) {
    my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'.
       $env{'form.codethree'};
    my ($tsymb,$tuname,$tudom,$tcrsid)=
       &Apache::lonnet::checkin($token);
    if ($tsymb) {
       my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
       if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
    $request->print(&Apache::lonnet::ssi_body('/res/'.$url,
     ('grade_username' => $tuname,
      'grade_domain' => $tudom,
      'grade_courseid' => $tcrsid,
      'grade_symb' => $tsymb)));
       } else {
    $request->print('<h3>Not authorized: '.$token.'</h3>');
       }
    } else {
       $request->print('<h3>Not a valid DocID: '.$token.'</h3>');
    }
       } else {
    $request->print(&Apache::lonxml::tokeninputfield());
       }
    }
       } else {
    &init_perm();
    if ($command eq 'submission' && $perm{'vgr'}) {
       ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
    } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
       &pickStudentPage($request);
    } elsif ($command eq 'displayPage' && $perm{'vgr'}) {
       &displayPage($request);
    } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
       &updateGradeByPage($request);
    } elsif ($command eq 'processGroup' && $perm{'vgr'}) {
       &processGroup($request);
    } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
       $request->print(&grading_menu($request));
    } elsif ($command eq 'submit_options' && $perm{'vgr'}) {
       $request->print(&submit_options($request));
    } elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
       $request->print(&viewgrades($request));
    } elsif ($command eq 'handgrade' && $perm{'mgr'}) {
       $request->print(&processHandGrade($request));
    } elsif ($command eq 'editgrades' && $perm{'mgr'}) {
       $request->print(&editgrades($request));
    } elsif ($command eq 'verify' && $perm{'vgr'}) {
       $request->print(&verifyreceipt($request));
           } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
               $request->print(&process_clicker($request));
           } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
               $request->print(&process_clicker_file($request));
           } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
               $request->print(&assign_clicker_grades($request));
    } elsif ($command eq 'csvform' && $perm{'mgr'}) {
       $request->print(&upcsvScores_form($request));
    } elsif ($command eq 'csvupload' && $perm{'mgr'}) {
       $request->print(&csvupload($request));
    } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
       $request->print(&csvuploadmap($request));
    } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
       if ($env{'form.associate'} ne 'Reverse Association') {
    $request->print(&csvuploadoptions($request));
       } else {
    if ( $env{'form.upfile_associate'} ne 'reverse' ) {
       $env{'form.upfile_associate'} = 'reverse';
    } else {
       $env{'form.upfile_associate'} = 'forward';
    }
    $request->print(&csvuploadmap($request));
       }
    } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
       $request->print(&csvuploadassign($request));
    } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
       $request->print(&scantron_selectphase($request));
     } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
        $request->print(&scantron_do_warning($request));
    } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
       $request->print(&scantron_validate_file($request));
    } elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
       $request->print(&scantron_process_students($request));
     } elsif ($command eq 'scantronupload' && 
     (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
     &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
        $request->print(&scantron_upload_scantron_data($request)); 
     } elsif ($command eq 'scantronupload_save' &&
     (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
     &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
        $request->print(&scantron_upload_scantron_data_save($request));
     } elsif ($command eq 'scantron_download' &&
    &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
        $request->print(&scantron_download_scantron_data($request));
    } elsif ($command) {
       $request->print("Access Denied ($command)");
    }
       }
       $request->print(&Apache::loncommon::end_page());
       &reset_caches();
       return '';
 }  }
   
 1;  1;

Removed from v.1.38  
changed lines
  Added in v.1.489


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>