Diff for /loncom/homework/grades.pm between versions 1.36 and 1.336

version 1.36, 2002/07/07 20:08:45 version 1.336, 2006/03/19 21:38:55
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::loncoursedata;
   use Apache::lonmsg qw(:user_normal_msg);
 use Apache::Constants qw(:common);  use Apache::Constants qw(:common);
   use Apache::lonlocal;
   use String::Similarity;
   use POSIX qw(floor);
   
 sub moreinfo {  my %oldessays=();
   my ($request,$reason) = @_;  my %perm=();
   $request->print("Unable to process request: $reason");  
   if ( $Apache::grades::viewgrades eq 'F' ) {  # ----- These first few routines are general use routines.----
     $request->print('<form action="/adm/grades" method="post">'."\n");  #
     if ($ENV{'form.url'}) {  # --- Retrieve the parts from the metadata file.---
       $request->print('<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />'."\n");  sub getpartlist {
     }      my ($symb) = @_;
     if ($ENV{'form.symb'}) {      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
       $request->print('<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />'."\n");      my $partorder = &Apache::lonnet::metadata($url, 'partorder');
     }      my @parts;
 #    $request->print('<input type="hidden" name="command" value="submission" />'."\n");      if ($partorder) {
     $request->print('<input type="hidden" name="command" value="'.$ENV{'form.command'}.'" />'."\n");   for my $part (split (/,/,$partorder)) {
     $request->print("Student:".'<input type="text" name="student" value="'.$ENV{'form.student'}.'" />'."<br />\n");      if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) {
     $request->print("Domain:".'<input type="text" name="domain" value="'.$ENV{'user.domain'}.'" />'."<br />\n");   push(@parts, $part);
     $request->print('<input type="submit" name="submit" value="ReSubmit" />'."<br />\n");      }
     $request->print('</form>');   }    
   }      } else {
   return '';   my $metadata = &Apache::lonnet::metadata($url, 'packages');
    foreach (split(/\,/,$metadata)) {
       if ($_ =~ /^part_(.*)$/) {
    if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) {
       push(@parts, $1);
    }
       }
    }
       }
       my @stores;
       foreach my $part (@parts) {
    my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
    foreach my $key (@metakeys) {
       if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
    }
       }
       return @stores;
 }  }
   
 sub verifyreceipt {  # --- Get the symbolic name of a problem and the url
     my $request=shift;  sub get_symb {
     my $courseid=$ENV{'request.course.id'};      my ($request,$silent) = @_;
 #    my $cdom=$ENV{"course.$courseid.domain"};      (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
 #    my $cnum=$ENV{"course.$courseid.num"};      my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
     my $receipt=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'.      if ($symb eq '') { 
                 $ENV{'form.receipt'};   if (!$silent) {
     $receipt=~s/[^\-\d]//g;      $request->print("Unable to handle ambiguous references:$url:.");
     my $symb=$ENV{'form.symb'};      return ();
     unless ($symb) {   }
  $symb=&Apache::lonnet::symbread($ENV{'form.url'});  
     }  
     if ((&Apache::lonnet::allowed('mgr',$courseid)) && ($symb)) {  
         $request->print('<h1>Verifying Submission Receipt '.$receipt.'</h1>');  
         my $matches=0;  
         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 '';      return ($symb);
 }  }
   
 sub student_gradeStatus {  #--- Format fullname, username:domain if different for display
   my ($url,$udom,$uname) = @_;  #--- Use anywhere where the student names are listed
   my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));  sub nameUserString {
   my %record= &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);      my ($type,$fullname,$uname,$udom) = @_;
   foreach my $part (&getpartlist($url)) {      if ($type eq 'header') {
     my ($temp,$part,$type)=split(/_/,$part);   return '<b>&nbsp;Fullname&nbsp;</b><font color="#999999">(Username)</font>';
     if ($type eq 'solved') {      } else {
       my ($status,$foo)=split(/_/,$record{"resource.$part.$type"},2);   return '&nbsp;'.$fullname.'<font color="#999999">&nbsp;('.$uname.
       $status = 'partial' if ($foo =~ /^partially/);      ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</font>';
       $status = 'nothing' if ($status eq '');  
       return $type,$status;  
     }      }
   }  
   return '';  
 }  }
   
 sub get_fullname {  #--- Get the partlist and the response type for a given problem. ---
     my ($sname,$sdom) = @_;  #--- Indicate if a response type is coded handgraded or not. ---
     my %name=&Apache::lonnet::get('environment', ['lastname','generation',  sub response_type {
   'firstname','middlename'],      my ($symb) = shift;
   $sdom,$sname);      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     my $fullname;      my $allkeys = &Apache::lonnet::metadata($url,'keys');
     my ($tmp) = keys(%name);      my %vPart;
     if ($tmp !~ /^(con_lost|error|no_such_host)/i) {      foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
  $fullname=$name{'lastname'}.$name{'generation'};   $vPart{$partid}=1;
  if ($fullname =~ /[^\s]+/) { $fullname.=', '; }      }
  $fullname.=$name{'firstname'}.' '.$name{'middlename'};      my %seen = ();
       my (@partlist,%handgrade,%responseType);
       foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {
    if (/^\w+response_.*/ || /^Task_/) {
       my ($responsetype,$part) = split(/_/,$_,2);
       my ($partid,$respid) = split(/_/,$part);
       if ($responsetype eq 'Task') { $respid='0'; }
       if (&Apache::loncommon::check_if_partid_hidden($partid,$symb)) {
    next;
       }
       if (%vPart && !exists($vPart{$partid})) {
    next;
       }
       $responsetype =~ s/response$//; # make it compatible w/ navmaps - should move to that!!
       my ($value) = &Apache::lonnet::EXT('resource.'.$part.'.handgrade',$symb);
       $handgrade{$part} = ($value eq 'yes' ? 'yes' : 'no'); 
       if (!exists($responseType{$partid})) { $responseType{$partid}={}; }
       $responseType{$partid}->{$respid}=$responsetype;
       next if ($seen{$partid} > 0);
       $seen{$partid}++;
       push @partlist,$partid;
    }
     }      }
     return $fullname;      return (\@partlist,\%handgrade,\%responseType);
 }  }
   
 sub listStudents {  sub get_display_part {
   my ($request) = shift;      my ($partID,$symb)=@_;
   my $cdom      =$ENV{"course.$ENV{'request.course.id'}.domain"};      my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
   my $cnum      =$ENV{"course.$ENV{'request.course.id'}.num"};      if (defined($display) and $display ne '') {
   my $getsec    =$ENV{'form.section'};   $display.= " (<font color=\"#999900\">id $partID</font>)";
   my $submitonly=$ENV{'form.submitonly'};      } else {
    $display=$partID;
   $request->print(<<ENDTABLEST);      }
 <h2><font color="#339933">&nbsp;View Submissions for a Student or a Group of Students</font></h2>      return $display;
 &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   
 &nbsp;&nbsp;&nbsp;<b>Submissions: </b><input type="radio" name="lastSub" value="last" checked> last   
 <input type="radio" name="lastSub" value="all"> all   
 <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 ($classlist) = &getclasslist($getsec,'0');  #--- Show resource title
   foreach my $student ( sort(@{ $$classlist{$getsec} }) ) {  #--- and parts and response type
       my ($sname,$sdom) = split(/:/,$student);  sub showResourceInfo {
       my ($type,$status) = &student_gradeStatus($ENV{'form.url'},$cdom,$sname);      my ($symb,$probTitle,$checkboxes) = @_;
       next if ($status eq 'nothing' && $submitonly eq 'yes');      my $col=3;
       if ($checkboxes) { $col=4; }
       my $fullname = &get_fullname($sname,$sdom);      my $result ='<table border="0">'.
       if ( $Apache::grades::viewgrades eq 'F' ) {   '<tr><td colspan="'.$col.'"><font size="+1"><b>'.&mt('Current Resource').': </b>'.
   $request->print("\n".'<tr bgcolor="#ffffe6">'.   $probTitle.'</font></td></tr>'."\n";
   '<td align="center"><input type=checkbox name="stuinfo" value="'.      my ($partlist,$handgrade,$responseType) = &response_type($symb);
   $student.':'.$fullname.'"></td>'."\n".      my %resptype = ();
   '<td>&nbsp;'.$sname.'&nbsp;</td>'."\n".      my $hdgrade='no';
   '<td>&nbsp;'.$fullname.'&nbsp;</td>'."\n".      my %partsseen;
   '<td align="middle">&nbsp;'.$sdom.'&nbsp;</td>'."\n");      for my $part_resID (sort keys(%$handgrade)) {
   $request->print('<td align="middle">&nbsp;'.$status.'&nbsp;</td>'."\n");   my $handgrade=$$handgrade{$part_resID};
    my ($partID,$resID) = split(/_/,$part_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='on' /></td>";
       }
       $partsseen{$partID}=1;
    }
    my $display_part=&get_display_part($partID,$symb);
    $result.='<td><b>Part: </b>'.$display_part.' <font color="#999999">'.
       $resID.'</font></td>'.
       '<td><b>Type: </b>'.$responsetype.'</td></tr>';
   #    '<td><b>Handgrade: </b>'.$handgrade.'</td></tr>';
       }
       $result.='</table>'."\n";
       return $result,$responseType,$hdgrade,$partlist,$handgrade;
   }
   
   $request->print('</tr>');  
       }  sub get_order {
   }      my ($partid,$respid,$symb,$uname,$udom)=@_;
   $request->print('</table></td></tr></table>');      my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
   $request->print('<input type="submit" name="submit" value="View/Grade" /><form />');      $url=&Apache::lonnet::clutter($url);
       my $subresult=&Apache::lonnet::ssi($url,
          ('grade_target' => 'analyze'),
          ('grade_domain' => $udom),
          ('grade_symb' => $symb),
          ('grade_courseid' => 
           $env{'request.course.id'}),
          ('grade_username' => $uname));
       (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
       my %analyze=&Apache::lonnet::str2hash($subresult);
       return ($analyze{"$partid.$respid.shown"});
   }
   #--- Clean response type for display
   #--- Currently filters option/rank/radiobutton/match/essay/Task
   #        response types only.
   sub cleanRecord {
       my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
    $uname,$udom) = @_;
       my $grayFont = '<font color="#999999">';
       if ($response =~ /^(option|rank)$/) {
    my %answer=&Apache::lonnet::str2hash($answer);
    my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
    my ($toprow,$bottomrow);
    foreach my $foil (@$order) {
       if ($grading{$foil} == 1) {
    $toprow.='<td><b>'.$answer{$foil}.'&nbsp;</b></td>';
       } else {
    $toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
       }
       $bottomrow.='<td>'.$grayFont.$foil.'</font>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.'Option ID</font></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;</font></b></td>';
       } else {
    $toprow.='<td><i>'.$item.'&nbsp;</i></td>';
    $middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</font></i></td>';
       }
       $bottomrow.='<td>'.$grayFont.$foil.'</font>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.'Item ID</font></td>'.
       $middlerow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'.
       $bottomrow.'</tr>'.'</table></blockquote>';
       } elsif ($response eq 'radiobutton') {
    my %answer=&Apache::lonnet::str2hash($answer);
    my ($toprow,$bottomrow);
    my $correct=($order->[0])+1;
    for (my $i=1;$i<=$#$order;$i++) {
       my $foil=$order->[$i];
       if (exists($answer{$foil})) {
    if ($i == $correct) {
       $toprow.='<td><b>true</b></td>';
    } else {
       $toprow.='<td><i>true</i></td>';
    }
       } else {
    $toprow.='<td>false</td>';
       }
       $bottomrow.='<td>'.$grayFont.$foil.'</font>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.'Option ID</font></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;
    }
          
       }
       return $answer;
 }  }
   
 sub processGroup {  #-- A couple of common js functions
   my ($request)  = shift;  sub commonJSfunctions {
   my $ctr        = 0;      my $request = shift;
   my @stuchecked = (ref($ENV{'form.stuinfo'}) ? @{$ENV{'form.stuinfo'}}      $request->print(<<COMMONJSFUNCTIONS);
                            : ($ENV{'form.stuinfo'}));  <script type="text/javascript" language="javascript">
   my $total      = scalar(@stuchecked)-1;      function radioSelection(radioButton) {
   if ($stuchecked[0] eq '') {   var selection=null;
       &userError($request,'No student was selected for viewing/grading.');   if (radioButton.length > 1) {
       return;      for (var i=0; i<radioButton.length; i++) {
   }   if (radioButton[i].checked) {
   foreach (@stuchecked) {      return radioButton[i].value;
       my ($sname,$sdom,$fullname) = split(/:/);   }
       $ENV{'form.student'} = $sname;      }
       $ENV{'form.fullname'} = $fullname;   } else {
       &submission($request,$ctr,$total);      if (radioButton.checked) return radioButton.value;
       $ctr++;   }
   }   return selection;
   return 'The End';      }
   
       function pullDownSelection(selectOne) {
    var selection="";
    if (selectOne.length > 1) {
       for (var i=0; i<selectOne.length; i++) {
    if (selectOne[i].selected) {
       return selectOne[i].value;
    }
       }
    } else {
               // only one value it must be the selected one
       return selectOne.value;
    }
       }
   </script>
   COMMONJSFUNCTIONS
 }  }
   
 sub userError {  #--- Dumps the class list with usernames,list of sections,
     my ($request, $reason, $step) = @_;  #--- section, ids and fullnames for each user.
     $request->print('<h3><font color="red">LON-CAPA User Error</font></h3><br />'."\n");  sub getclasslist {
     $request->print('<b>Reason: </b>'.$reason.'<br /><br />'."\n");      my ($getsec,$filterlist) = @_;
     $request->print('<b>Step: </b>'.($step ne '' ? $step : 'Use your browser back button to correct')      my @getsec;
     .'<br /><br />'."\n");      if (!ref($getsec)) {
     return '';   if ($getsec ne '' && $getsec ne 'all') {
       @getsec=($getsec);
    }
       } else {
    @getsec=@{$getsec};
       }
       if (grep(/^all$/,@getsec)) { undef(@getsec); }
   
       my $classlist=&Apache::loncoursedata::get_classlist();
       # Bail out if we were unable to get the classlist
       return if (! defined($classlist));
       #
       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()];
    # filter students according to status selected
    if ($filterlist && $env{'form.Status'} ne 'Any') {
       if ($env{'form.Status'} ne $status) {
    delete ($classlist->{$student});
    next;
       }
    }
    $section = ($section ne '' ? $section : 'none');
    if (&canview($section)) {
       if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
    $sections{$section}++;
    $fullnames{$student}=$fullname;
       } else {
    delete($classlist->{$student});
       }
    } else {
       delete($classlist->{$student});
    }
       }
       my %seen = ();
       my @sections = sort(keys(%sections));
       return ($classlist,\@sections,\%fullnames);
 }  }
   
 #FIXME - needs to handle multiple matches  sub canmodify {
 sub finduser {      my ($sec)=@_;
   my ($name) = @_;      if ($perm{'mgr'}) {
   my $domain = '';   if (!defined($perm{'mgr_section'})) {
   if ( $Apache::grades::viewgrades eq 'F' ) {      # can modify whole class
     my ($classlist) = &getclasslist('all','0');      return 1;
     foreach ( sort(@{ $$classlist{'all'} }) ) {   } else {
       my ($posname,$posdomain) = split(/:/);      if ($sec eq $perm{'mgr_section'}) {
       if ($posname =~ $name) { $name=$posname; $domain=$posdomain; last; }   #can modify the requested section
     }   return 1;
     return ($name,$domain);      } else {
   } else {   # can't modify the request section
     return ($ENV{'user.name'},$ENV{'user.domain'});   return 0;
   }      }
    }
       }
       #can't modify
       return 0;
 }  }
   
 sub getclasslist {  sub canview {
   my ($getsec,$hideexpired) = @_;      my ($sec)=@_;
   my ($coursedomain,$coursenum) = split(/_/,$ENV{'request.course.id'});      if ($perm{'vgr'}) {
   my %classlist=&Apache::lonnet::dump('classlist',$coursedomain,$coursenum);   if (!defined($perm{'vgr_section'})) {
   my $now = time;      # can modify whole class
   my (@holdsec,@sections);      return 1;
   foreach my $student (keys(%classlist)) {   } else {
     my ($end,$start)=split(/:/,$classlist{$student});      if ($sec eq $perm{'vgr_section'}) {
     # still a student?   #can modify the requested section
     if (($hideexpired) && ($end) && ($end < $now)) {   return 1;
       next;      } else {
     }   # can't modify the request section
     my ($unam,$udom) = split(/:/,$student,2);   return 0;
     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);      #can't modify
   }      return 0;
   my %seen = ();  
   foreach my $item (@holdsec) {  
       push (@sections, $item) unless $seen{$item}++;  
   }  
   return (\%classlist,\@sections);  
 }  }
   
 sub getpartlist {  #--- Retrieve the grade status of a student for all the parts
   my ($url) = @_;  sub student_gradeStatus {
   my @parts =();      my ($symb,$udom,$uname,$partlist) = @_;
   my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));      my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
   foreach my $key (@metakeys) {      my %partstatus = ();
     if ( $key =~ m/stores_([0-9]+)_.*/) {      foreach (@$partlist) {
       push(@parts,$key);   my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
    $status              = 'nothing' if ($status eq '');
    $partstatus{$_}      = $status;
    my $subkey           = "resource.$_.submitted_by";
    $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
     }      }
   }      return %partstatus;
   return @parts;  
 }  }
   
 sub viewstudentgrade {  # hidden form and javascript that calls the form
   my ($url,$symb,$courseid,$student,@parts) = @_;  # Use by verifyscript and viewgrades
   my $result ='';  # Shows a student's view of problem and submission
   my $cellclr = '"#ffffdd"';  sub jscriptNform {
   my ($username,$domain) = split(/:/,$student);      my ($symb) = @_;
       my $jscript='<script type="text/javascript" language="javascript">'."\n".
   my $fullname = &get_fullname($username,$domain);   '    function viewOneStudent(user,domain) {'."\n".
   my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$username);   ' document.onestudent.student.value = user;'."\n".
    ' document.onestudent.userdom.value = domain;'."\n".
   $result.="<tr bgcolor=$cellclr><td>$username</td><td>$fullname</td><td align=\"middle\">$domain</td>\n";   ' document.onestudent.submit();'."\n".
   foreach my $part (@parts) {   '    }'."\n".
     my ($temp,$part,$type)=split(/_/,$part);   '</script>'."\n";
     my $score=$record{"resource.$part.$type"};      $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
     if ($type eq 'awarded' || $type eq 'tries') {   '<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
       $result.='<td align="middle"><input type="text" name="GRADE.'.$student.'.'.$part.'.'.$type.   '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
   '" value="'.$score.'" size="4" /></td>'."\n";   '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
     } elsif ($type eq 'solved') {   '<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
       my ($status,$foo)=split(/_/,$score,2);   '<input type="hidden" name="command" value="submission" />'."\n".
       $result.="<td align=\"middle\"><select name=\"GRADE.$student.$part.$type\">\n";   '<input type="hidden" name="student" value="" />'."\n".
       my $optsel = '<option>correct</option><option>incorrect</option><option>excused</option>'.   '<input type="hidden" name="userdom" value="" />'."\n".
   '<option>ungraded</option><option>partial</option><option>nothing</option>'."\n";   '</form>'."\n";
       $status = 'nothing' if ($status eq '');      return $jscript;
       $optsel =~ s/<option>$status/<option selected="on">$status/;  }
       $result.=$optsel;  
       $result.="</select></td>\n";  # Given the score (as a number [0-1] and the weight) what is the final
   # point value? This function will round to the nearest tenth, third,
   # or quarter if one of those is within the tolerance of .00001.
   sub compute_points {
       my ($score, $weight) = @_;
       
       my $tolerance = .00001;
       my $points = $score * $weight;
   
       # Check for nearness to 1/x.
       my $check_for_nearness = sub {
           my ($factor) = @_;
           my $num = ($points * $factor) + $tolerance;
           my $floored_num = floor($num);
           if ($num - $floored_num < 2 * $tolerance * $factor) {
               return $floored_num / $factor;
           }
           return $points;
       };
   
       $points = $check_for_nearness->(10);
       $points = $check_for_nearness->(3);
       $points = $check_for_nearness->(4);
       
       return $points;
   }
   
   #------------------ End of general use routines --------------------
   
   #
   # Find most similar essay
   #
   
   sub most_similar {
       my ($uname,$udom,$uessay)=@_;
   
   # 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 %oldessays) {
    my ($tname,$tdom,$tcrsid)=split(/\./,$tkey);
   # ... except the same student
           if (($tname ne $uname) || ($tdom ne $udom)) {
       my $tessay=$oldessays{$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=$oldessays{$tkey};
               }
           } 
       }
       if ($limit>0.6) {
          return ($sname,$sdom,$scrsid,$sessay,$limit);
       } else {
          return ('','','','',0);
     }      }
   }  
   $result.='<td></td></tr>';  
   return $result;  
 }  }
   
 #FIXME need to look at the metadata <stores> spec on what type of data to accept and provide an  #-------------------------------------------------------------------
 #interface based on that, also do that to above function.  
 sub setstudentgrade {  #------------------------------------ Receipt Verification Routines
   my ($url,$symb,$courseid,$student,@parts) = @_;  #
   print "set student grade parts=@parts<br>";  #--- Check whether a receipt number is valid.---
   my $result ='';  sub verifyreceipt {
   my ($stuname,$domain) = split(/:/,$student);      my $request  = shift;
   my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$stuname);  
   my %newrecord;      my $courseid = $env{'request.course.id'};
       my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
   foreach my $part (@parts) {   $env{'form.receipt'};
     my ($temp,$part,$type)=split(/_/,$part);      $receipt     =~ s/[^\-\d]//g;
     my $oldscore=$record{"resource.$part.$type"};      my $symb     = &Apache::lonnet::symbread();
     my $newscore=$ENV{"form.GRADE.$student.$part.$type"};  
     print "old=$oldscore:new=$newscore:<br>";      my $title.='<h3><font color="#339933">Verifying Submission Receipt '.
     if ($type eq 'solved') {   $receipt.'</h3></font>'."\n".
       my $update=0;   '<font size=+1><b>Resource: </b>'.$env{'form.probTitle'}.'</font><br /><br />'."\n";
       if ($newscore eq 'nothing' ) {  
  if ($oldscore ne '') {      my ($string,$contents,$matches) = ('','',0);
   $update=1;      my (undef,undef,$fullname) = &getclasslist('all','0');
   $newscore = '';      
  }      my $receiptparts=0;
       } elsif ($oldscore !~ m/^$newscore/) {      if ($env{"course.$courseid.receiptalg"} eq 'receipt2') { $receiptparts=1; }
  $update=1;      my $parts=['0'];
  $result.="Updating $stuname to $newscore<br />\n";      if ($receiptparts) { ($parts)=&response_type($symb); }
  if ($newscore eq 'correct')   { $newscore = 'correct_by_override'; }      foreach (sort 
  if ($newscore eq 'incorrect') { $newscore = 'incorrect_by_override'; }       {
  if ($newscore eq 'excused')   { $newscore = 'excused'; }   if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
  if ($newscore eq 'ungraded')  { $newscore = 'ungraded_attempted'; }       return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
  if ($newscore eq 'partial')   { $newscore = 'correct_partially_by_override'; }   }
       } else {   return $a cmp $b;
  #$result.="$stuname:$part:$type:unchanged  $oldscore to $newscore:<br />\n";       } (keys(%$fullname))) {
       }   my ($uname,$udom)=split(/\:/);
       if ($update) { $newrecord{"resource.$part.$type"}=$newscore; }   foreach my $part (@$parts) {
       if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
    $contents.='<tr bgcolor="#ffffe6"><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.='</tr>'."\n";
   
    $matches++;
       }
    }
       }
       if ($matches == 0) {
    $string = $title.'No match found for the above receipt.';
     } else {      } else {
       if ($oldscore ne $newscore) {   $string = &jscriptNform($symb).$title.
  $newrecord{"resource.$part.$type"}=$newscore;      'The above receipt matches the following student'.
  $result.="Updating $student"."'s status for $part.$type to $newscore<br />\n";      ($matches <= 1 ? '.' : 's.')."\n".
       } else {      '<table border="0"><tr><td bgcolor="#777777">'."\n".
  #$result.="$stuname:$part:$type:unchanged  $oldscore to $newscore:<br />\n";      '<table border="0"><tr bgcolor="#e6ffff">'."\n".
       }      '<td><b>&nbsp;Fullname&nbsp;</b></td>'."\n".
       '<td><b>&nbsp;Username&nbsp;</b></td>'."\n".
       '<td><b>&nbsp;Domain&nbsp;</b></td>';
    if ($receiptparts) {
       $string.='<td>&nbsp;Problem Part&nbsp;</td>';
    }
    $string.='</tr>'."\n".$contents.
       '</table></td></tr></table>'."\n";
     }      }
   }      return $string.&show_grading_menu_form($symb);
   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";  #--- This is called by a number of programs.
   }  #--- Called from the Grading Menu - View/Grade an individual student
   return $result;  #--- 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 $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><font color="#339933">&nbsp;'.$viewgrade.
    ' Submissions for a Student or a Group of Students</font></h3>';
   
       my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
   
       $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 = "a student or group of students";
    } else {
       if (checkBox.checked) {
    ctr = 1;
       }
       sense = "the student";
    }
    if (ctr == 0) {
       alert("Please select "+sense+" before clicking on the Next button.");
       return false;
    }
    document.gradesub.submit();
       }
   
       function reLoadList(formname) {
    if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
    formname.command.value = 'submission';
    formname.submit();
       }
   </script>
   LISTJAVASCRIPT
   
       &commonJSfunctions($request);
       $request->print($result);
   
       my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked' : '';
       my $checklastsub = $checkhdgrade eq '' ? 'checked' : '';
       my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
    "\n".$table.
    '&nbsp;<b>View Problem Text: </b><label><input type="radio" name="vProb" value="no" checked="on" /> no </label>'."\n".
    '<label><input type="radio" name="vProb" value="yes" /> one student </label>'."\n".
    '<label><input type="radio" name="vProb" value="all" /> all students </label><br />'."\n".
    '&nbsp;<b>View Answer: </b><label><input type="radio" name="vAns" value="no"  /> no </label>'."\n".
    '<label><input type="radio" name="vAns" value="yes" /> one student </label>'."\n".
    '<label><input type="radio" name="vAns" value="all" checked="on" /> all students </label><br />'."\n".
    '&nbsp;<b>Submissions: </b>'."\n";
       if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
    $gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n";
       }
   
       my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'};
       $env{'form.Status'} = $saveStatus;
   
       $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n".
    '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n".
    '<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n".
    '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n".
    '<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".
    '<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="'.$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="'.$env{'form.Status'}.'" />'."\n";
       } else {
    $gradeTable.='<b>Student Status:</b> '.
       &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';
       }
   
       $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '.
    'next to the student\'s name(s). Then click on the Next button.<br />'."\n".
    '<input type="hidden" name="command" value="processGroup" />'."\n";
   
   # checkall buttons
       $gradeTable.=&check_script('gradesub', 'stuinfo');
       $gradeTable.='<input type="button" '."\n".
    'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
    'value="Next->" /> <br />'."\n";
       $gradeTable.=&check_buttons();
       $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="on" />Check For Plagiarism</label>';
       my ($classlist, undef, $fullname) = &getclasslist($getsec,'1');
       $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.
    '<table border="0"><tr bgcolor="#e6ffff">';
       my $loop = 0;
       while ($loop < 2) {
    $gradeTable.='<td><b>&nbsp;No.</b>&nbsp;</td><td><b>&nbsp;Select&nbsp;</b></td>'.
       '<td>'.&nameUserString('header').'&nbsp;Section/Group</td>';
    if ($env{'form.showgrading'} eq 'yes' 
       && $submitonly ne 'queued'
       && $submitonly ne 'all') {
       foreach (sort(@$partlist)) {
    my $display_part=&get_display_part((split(/_/))[0],$symb);
    $gradeTable.='<td><b>&nbsp;Part: '.$display_part.
       ' Status&nbsp;</b></td>';
       }
    } elsif ($submitonly eq 'queued') {
       $gradeTable.='<td><b>&nbsp;'.&mt('Queue Status').'&nbsp;</b></td>';
    }
    $loop++;
   # $gradeTable.='<td></td>' if ($loop%2 ==1);
       }
       $gradeTable.='</tr>'."\n";
   
       my $ctr = 0;
       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);
   
    my %status = ();
   
    if ($submitonly eq 'queued') {
       my %queue_status = 
    &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
    $udom,$uname);
       next if (!defined($queue_status{'gradingqueue'}));
       $status{'gradingqueue'} = $queue_status{'gradingqueue'};
    }
   
    if ($env{'form.showgrading'} eq 'yes' 
       && $submitonly ne 'queued'
       && $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');
    }
   
    $ctr++;
    my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
   
    if ( $perm{'vgr'} eq 'F' ) {
       $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);
       $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
                  '<td align="center"><label><input type=checkbox name="stuinfo" value="'.
                  $student.':'.$$fullname{$student}.':::SECTION'.$section.
          ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
          &nameUserString(undef,$$fullname{$student},$uname,$udom).
          '&nbsp;'.$section.'</td>'."\n";
   
       if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
    foreach (sort keys(%status)) {
       next if (/^resource.*?submitted_by$/);
       $gradeTable.='<td align="center">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";
    }
       }
   #    $gradeTable.='<td></td>' if ($ctr%2 ==1);
       $gradeTable.='</tr>'."\n" if ($ctr%2 ==0);
    }
       }
       if ($ctr%2 ==1) {
    $gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
       if ($env{'form.showgrading'} eq 'yes' 
    && $submitonly ne 'queued'
    && $submitonly ne 'all') {
    foreach (@$partlist) {
       $gradeTable.='<td>&nbsp;</td>';
    }
       } elsif ($submitonly eq 'queued') {
    $gradeTable.='<td>&nbsp;</td>';
       }
    $gradeTable.='</tr>';
       }
   
       $gradeTable.='</table></td></tr></table>'."\n".
    '<input type="button" '.
    'onClick="javascript:checkSelect(this.form.stuinfo);" '.
    'value="Next->" /></form>'."\n";
       if ($ctr == 0) {
    my $num_students=(scalar(keys(%$fullname)));
    if ($num_students eq 0) {
       $gradeTable='<br />&nbsp;<font color="red">There are no students currently enrolled.</font>';
    } 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;<font color="red">'.
    'No '.$submissions.' found for this resource for any students. ('.$num_students.
    ' students checked for '.$submissions.')</font><br />';
    }
       } elsif ($ctr == 1) {
    $gradeTable =~ s/type=checkbox/type=checkbox checked/;
       }
       $gradeTable.=&show_grading_menu_form($symb);
       $request->print($gradeTable);
       return '';
 }  }
   
 #  #---- Called from the listStudents routine
 # --------------------------- show submissions of a student, option to grade --------  
 sub submission {  sub check_script {
   my ($request,$counter,$total) = @_;      my ($form, $type)=@_;
       my $chkallscript='<script type="text/javascript">
       function checkall() {
           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=true;
                                          }
           }
       }
   
       function checksec() {
           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;
               }
           }
       }
   
   if ($counter == 0) {  
       $request->print(<<SUBJAVASCRIPT);      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;
                                          }
           }
       }
   
   </script>'."\n";
       return $chkallscript;
   }
   
   sub check_buttons {
       my $buttons.='<input type="button" onclick="checkall()" value="Check All" />';
       $buttons.='<input type="button" onclick="uncheckall()" value="Uncheck All" />&nbsp;';
       $buttons.='<input type="button" onclick="checksec()" value="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 (@stuchecked) {
    my ($uname,$udom,$fullname) = split(/:/);
    $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 {
       my $request = shift;
       $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(formsel,pts,wgt) {      function updateSelect(formname,id) {
     if (pts == 0) {   formname["GD_SEL"+id][0].selected = true;
       formsel[1].selected = true;   return;
       }
   
   //=========== Check that a point is assigned for all the parts  ============
       function checksubmit(formname,val,total,parttot) {
    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();
     }      }
     if (pts == wgt) {  </script>
       formsel[0].selected = true;  SUBJAVASCRIPT
   }
   
   #--- javascript for essay type problem --
   sub sub_page_kw_js {
       my $request = shift;
       my $iconpath = $request->dir_config('lonIconsURL');
       &commonJSfunctions($request);
       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 413  sub submission { Line 1169  sub submission {
     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?",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;
     displaySubject(subject);      var msgchk = document.SCORE["includemsg"+usrctr].value;
       re = /msgsub/;
       var shwsel = "";
       if (re.test(msgchk)) { shwsel = "checked" }
       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 message = eval("document.SCORE.savemsg"+i+".value");   var testmsg = "savemsg"+i+",";
  displaySavedMsg(i,message);   re = new RegExp(testmsg,"g");
    shwsel = "";
    if (re.test(msgchk)) { shwsel = "checked" }
    var message = document.SCORE["savemsg"+i].value;
    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;
     newMsg(newmsg);      shwsel = "";
       re = /newmsg/;
       if (re.test(msgchk)) { shwsel = "checked" }
       newMsg(newmsg,shwsel);
     msgTail();       msgTail(); 
     return;      return;
   }    }
   
 function savedMsgHeader(Nmsg,usrctr,fullname) {    function checkEntities(strx) {
     var height = 30*Nmsg+250;      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) {
       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";
     }      }
     pWin = window.open('', 'MessageCenter', 'toolbar=no,location=no,scrollbars='+scrollbar+',width=600,height='+height);      var xpos = (screen.width-600)/2;
     pWin.document.write("<html><head>");      xpos = (xpos < 0) ? '0' : xpos;
     pWin.document.write("<title>Message Central</title>");      var ypos = (screen.height-height)/2-30;
       ypos = (ypos < 0) ? '0' : ypos;
     pWin.document.write("<script language=javascript>");  
     pWin.document.write("function checkInput() {");      pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
     pWin.document.write("  opener.document.SCORE.msgsub.value = document.msgcenter.msgsub.value;");      pWin.focus();
     pWin.document.write("  var nmsg   = opener.document.SCORE.savemsgN.value;");      pDoc = pWin.document;
     pWin.document.write("  var usrctr = document.msgcenter.usrctr.value;");      pDoc.$docopen;
     pWin.document.write("  var newval = eval(\\"opener.document.SCORE.newmsg\\"+usrctr);");      pDoc.write("<html><head>");
     pWin.document.write("  newval.value = document.msgcenter.newmsg.value;");      pDoc.write("<title>Message Central</title>");
   
     pWin.document.write("  var msgchk = \\"\\";");      pDoc.write("<script language=javascript>");
     pWin.document.write("  if (document.msgcenter.subchk.checked) {");      pDoc.write("function checkInput() {");
     pWin.document.write("     msgchk = \\"subject,\\";");      pDoc.write("  opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);");
     pWin.document.write("  }");      pDoc.write("  var nmsg   = opener.document.SCORE.savemsgN.value;");
     pWin.document.write(   "for (var i=1; i<=nmsg; i++) {");      pDoc.write("  var usrctr = document.msgcenter.usrctr.value;");
     pWin.document.write("      var opnmsg = eval(\\"opener.document.SCORE.savemsg\\"+i);");      pDoc.write("  var newval = opener.document.SCORE[\\"newmsg\\"+usrctr];");
     pWin.document.write("      var frmmsg = eval(\\"document.msgcenter.msg\\"+i);");      pDoc.write("  newval.value = opener.checkEntities(document.msgcenter.newmsg.value);");
     pWin.document.write("      opnmsg.value = frmmsg.value;");  
     pWin.document.write("      var chkbox = eval(\\"document.msgcenter.msgn\\"+i);");      pDoc.write("  var msgchk = \\"\\";");
     pWin.document.write("      if (chkbox.checked) {");      pDoc.write("  if (document.msgcenter.subchk.checked) {");
     pWin.document.write("         msgchk += i+\\",\\";");      pDoc.write("     msgchk = \\"msgsub,\\";");
     pWin.document.write("      }");      pDoc.write("  }");
     pWin.document.write("  }");      pDoc.write("  var includemsg = 0;");
     pWin.document.write("  if (document.msgcenter.newmsgchk.checked) {");      pDoc.write("  for (var i=1; i<=nmsg; i++) {");
     pWin.document.write("     msgchk += \\"new\\";");      pDoc.write("      var opnmsg = opener.document.SCORE[\\"savemsg\\"+i];");
     pWin.document.write("  }");      pDoc.write("      var frmmsg = document.msgcenter[\\"msg\\"+i];");
     pWin.document.write("  var includemsg = eval(\\"opener.document.SCORE.includemsg\\"+usrctr);");      pDoc.write("      opnmsg.value = opener.checkEntities(frmmsg.value);");
     pWin.document.write("  includemsg.value = msgchk;");      pDoc.write("      var showflg = opener.document.SCORE[\\"shownOnce\\"+i];");
       pDoc.write("      showflg.value = \\"1\\";");
     pWin.document.write("  self.close()");      pDoc.write("      var chkbox = document.msgcenter[\\"msgn\\"+i];");
       pDoc.write("      if (chkbox.checked) {");
     pWin.document.write("}");      pDoc.write("         msgchk += \\"savemsg\\"+i+\\",\\";");
       pDoc.write("         includemsg = 1;");
     pWin.document.write("<");      pDoc.write("      }");
     pWin.document.write("/script>");      pDoc.write("  }");
       pDoc.write("  if (document.msgcenter.newmsgchk.checked) {");
     pWin.document.write("</head><body bgcolor=white>");      pDoc.write("     msgchk += \\"newmsg\\"+usrctr;");
       pDoc.write("     includemsg = 1;");
     pWin.document.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");      pDoc.write("  }");
     pWin.document.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");      pDoc.write("  imgformname = opener.document.SCORE[\\"mailicon\\"+usrctr];");
     pWin.document.write("<font color=\\"green\\" size=+1>&nbsp;Compose Message for \"+fullname+\"</font><br><br>");      pDoc.write("  imgformname.src = \\"$iconpath/\\"+((includemsg) ? \\"mailto.gif\\" : \\"mailbkgrd.gif\\");");
       pDoc.write("  var includemsg = opener.document.SCORE[\\"includemsg\\"+usrctr];");
     pWin.document.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");      pDoc.write("  includemsg.value = msgchk;");
     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>");      pDoc.write("  self.close()");
 }  
     function displaySubject(msg) {      pDoc.write("}");
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");  
     pWin.document.write("<td>Subject</td>");      pDoc.write("<");
     pWin.document.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\" checked></td>");      pDoc.write("/script>");
     pWin.document.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+" \\"size=\\"60\\" maxlength=\\"80\\"></td></tr>");  
 }      pDoc.write("</head><body bgcolor=white>");
   
 function displaySavedMsg(ctr,msg) {      pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
     pWin.document.write("<td align=\\"center\\">"+ctr+"</td>");      pDoc.write("<font color=\\"green\\" size=+1>&nbsp;Compose Message for \"+fullname+\"</font><br /><br />");
     pWin.document.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"></td>");  
     pWin.document.write("<td><input name=\\"msg"+ctr+"\\" type=\\"text\\" value=\\""+msg+" \\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");      pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
 }      pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
       pDoc.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>");
 function newMsg(newmsg) {  }
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      function displaySubject(msg,shwsel) {
     pWin.document.write("<td align=\\"center\\">New</td>");      pDoc = pWin.document;
     pWin.document.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"></td>");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td><input name=\\"newmsg\\" type=\\"text\\" value=\\""+newmsg+" \\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");      pDoc.write("<td>Subject</td>");
 }      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 msgTail() {  }
     pWin.document.write("</table>");  
     pWin.document.write("</td></tr></table>&nbsp;");    function displaySavedMsg(ctr,msg,shwsel) {
     pWin.document.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");      pDoc = pWin.document;
     pWin.document.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("</form>");      pDoc.write("<td align=\\"center\\">"+ctr+"</td>");
     pWin.document.write("</body></html>");      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) {
       pDoc = pWin.document;
       pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
       pDoc.write("<td align=\\"center\\">New</td>");
       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() {
       pDoc = pWin.document;
       pDoc.write("</table>");
       pDoc.write("</td></tr></table>&nbsp;");
       pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
       pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
       pDoc.write("</form>");
       pDoc.write("</body></html>");
       pDoc.close();
 }  }
   
 //====================== Script for keyword highlight options ==============  //====================== Script for keyword highlight options ==============
Line 555  function msgTail() { Line 1370  function msgTail() {
     return;      return;
   }    }
   
     function highlightCentral() {
 function highlightCentral() {  //    if (window.hwdWin) window.hwdWin.close();
     hwdWin = window.open('', 'KeywordHighlightCentral', 'toolbar=no,location=no,scrollbars=no,width=400,height=300');      var xpos = (screen.width-400)/2;
     hwdWin.document.write("<html><head>");      xpos = (xpos < 0) ? '0' : xpos;
     hwdWin.document.write("<title>Highlight Central</title>");      var ypos = (screen.height-330)/2-30;
       ypos = (ypos < 0) ? '0' : ypos;
     hwdWin.document.write("<script language=javascript>");  
     hwdWin.document.write("function updateChoice() {");      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.kwclr.value = radioSelection(document.hlCenter.kwdclr);");      hwdWin.focus();
     hwdWin.document.write("  opener.document.SCORE.kwsize.value = radioSelection(document.hlCenter.kwdsize);");      var hDoc = hwdWin.document;
     hwdWin.document.write("  opener.document.SCORE.kwstyle.value = radioSelection(document.hlCenter.kwdstyle);");      hDoc.$docopen;
     hwdWin.document.write("  self.close()");      hDoc.write("<html><head>");
     hwdWin.document.write("}");      hDoc.write("<title>Highlight Central</title>");
   
     hwdWin.document.write("function radioSelection(radioButton) {");      hDoc.write("<script language=javascript>");
     hwdWin.document.write("    var selection=null;");      hDoc.write("function updateChoice(flag) {");
     hwdWin.document.write("    for (var i=0; i<radioButton.length; i++) {");      hDoc.write("  opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);");
     hwdWin.document.write("        if (radioButton[i].checked) {");      hDoc.write("  opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);");
     hwdWin.document.write("            selection=radioButton[i].value;");      hDoc.write("  opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);");
     hwdWin.document.write("            return selection;");      hDoc.write("  opener.document.SCORE.refresh.value = \\"on\\";");
     hwdWin.document.write("        }");      hDoc.write("  if (opener.document.SCORE.keywords.value!=\\"\\"){");
     hwdWin.document.write("    }");      hDoc.write("     opener.document.SCORE.submit();");
     hwdWin.document.write("}");      hDoc.write("  }");
       hDoc.write("  self.close()");
     hwdWin.document.write("<");      hDoc.write("}");
     hwdWin.document.write("/script>");  
       hDoc.write("<");
     hwdWin.document.write("</head><body bgcolor=white>");      hDoc.write("/script>");
   
     hwdWin.document.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");      hDoc.write("</head><body bgcolor=white>");
     hwdWin.document.write("<font color=\\"green\\" size=+1>&nbsp;Keyword Highlight Options</font><br><br>");  
       hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
     hwdWin.document.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");      hDoc.write("<font color=\\"green\\" size=+1>&nbsp;Keyword Highlight Options</font><br /><br />");
     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>");      hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
 }      hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
       hDoc.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>");
 function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) {     }
     hwdWin.document.write("<tr bgcolor=\\"#ffffdd\\">");  
     hwdWin.document.write("<td align=\\"left\\">");    function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
     hwdWin.document.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"</td>");      var hDoc = hwdWin.document;
     hwdWin.document.write("<td align=\\"left\\">");      hDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     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=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"</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=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"</td>");
  }      hDoc.write("<td align=\\"left\\">");
       hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"</td>");
 function highlightend() {       hDoc.write("</tr>");
     hwdWin.document.write("</table>");    }
     hwdWin.document.write("</td></tr></table>&nbsp;");  
     hwdWin.document.write("<input type=\\"button\\" value=\\"Set Options\\" onClick=\\"javascript:updateChoice()\\">&nbsp;&nbsp;");    function highlightend() { 
     hwdWin.document.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");      var hDoc = hwdWin.document;
     hwdWin.document.write("</form>");      hDoc.write("</table>");
     hwdWin.document.write("</body></html>");      hDoc.write("</td></tr></table>&nbsp;");
 }      hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");
       hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
       hDoc.write("</form>");
       hDoc.write("</body></html>");
       hDoc.close();
     }
   
 </script>  </script>
 SUBJAVASCRIPT  SUBJAVASCRIPT
 }  }
   (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 ($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) {  
       $request->print('<h2>&nbsp;<font color="#339933">Submission Record</font></h2>'.  
       '<font size=+1>&nbsp;<b>Resource: </b>'.$url.'</font>');  
   }  
   
   #--- 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 src="'.$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 ? '(problem weight)' : 
     '<font color="red">problem weight assigned by computer</font>');
       $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><td>'.
    '<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";
       my $ctr = 0;
       $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
       while ($ctr<=$wgt) {
    $result.= '<td><nobr><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
       'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
       $ctr.')" value="'.$ctr.'" '.
       ($score eq $ctr ? 'checked':'').' /> '.$ctr."</label></nobr></td>\n";
    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
    $ctr++;
       }
       $result.='</tr></table>';
       $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>'."\n";
       $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
    ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
    'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
    $wgt.')" /></td>'."\n";
       $result.='<td>/'.$wgt.' '.$wgtmsg.
    ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
    ' </td><td>'."\n";
       $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
    'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
       if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
    $result.='<option> </option>'.
       '<option selected="on">excused</option>';
       } else {
    $result.='<option selected="on"> </option>'.
       '<option>excused</option>';
       }
       $result.='<option>reset status</option></select>'."\n";
       $result.="&nbsp&nbsp\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.='</td></tr></table>'."\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);
       foreach my $part_resp (sort(keys(%$handgrade))) {
           my ($part,$resp) = split(/_/,$part_resp);
           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) {
       my ($file_disp) = ($file =~ m|.+/(.+)$|);
       $result.=&mt('Return commented version of [_1] to student.',
    '<span class="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 />';
       $file_counter++;
    }
       }
       return $result;    
   }
   
   sub show_problem {
       my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode) = @_;
       my $rendered;
       &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'});
       }
       if ($removeform) {
    $rendered=~s|<form(.*?)>||g;
    $rendered=~s|</form>||g;
    $rendered=~s|name="submit"|name="would_have_been_submit"|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'});
       }
       if ($removeform) {
    $companswer=~s|<form(.*?)>||g;
    $companswer=~s|</form>||g;
    $companswer=~s|name="submit"|name="would_have_been_submit"|g;
       }
       my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';
       $result.='<table border="0" width="100%">';
       if ($viewon) {
    $result.='<tr><td bgcolor="#e6ffff"><b> ';
    if ($mode eq 'both' or $mode eq 'text') {
       $result.='View of the problem - ';
    } else {
       $result.='Correct answer: ';
    }
    $result.=$env{'form.fullname'}.'</b></td></tr>';
       }
       if ($mode eq 'both') {
    $result.='<tr><td bgcolor="#ffffff">'.$rendered.'<br />';
    $result.='<b>Correct answer:</b><br />'.$companswer;
       } elsif ($mode eq 'text') {
    $result.='<tr><td bgcolor="#ffffff">'.$rendered;
       } elsif ($mode eq 'answer') {
    $result.='<tr><td bgcolor="#ffffff">'.$companswer;
       }
       $result.='</td></tr></table>';
       $result.='</td></tr></table><br />';
       return $result;
   }
   
   # --------------------------- 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('<font color="red">Unable to view requested student.('.
    $uname.'@'.$udom.' in section '.$usec.' in course id '.
    $env{'request.course.id'}.')</font>');
    $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 src="'.$request->dir_config('lonIconsURL').
    '/check.gif" height="16" border="0" />';
   
       # 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'};
   
    $request->print('<h3>&nbsp;<font color="#339933">Submission Record</font></h3>'."\n".
    '<font size=+1>&nbsp;<b>Resource: </b>'.$env{'form.probTitle'}.'</font>'."\n");
   
    if ($env{'form.handgrade'} eq 'no') {
       my $checkMark='<br /><br />&nbsp;<b>Note:</b> Part(s) graded correct by the computer is marked with a '.
    $checkIcon.' symbol.'."\n";
       $request->print($checkMark);
    }
   
    # 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'};
   
    $request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
    '<input type="hidden" name="command"    value="handgrade" />'."\n".
    '<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
    '<input type="hidden" name="Status"     value="'.$env{'form.Status'}.'" />'."\n".
    '<input type="hidden" name="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="'.$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".
    '<input type="hidden" name="section"    value="'.$env{'form.section'}.'" />'."\n".
    '<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') {
 #  #
 # option to display problem, only once else it cause problems with the form later since the problem has a form.  # Print out the keyword options line
   if ($ENV{'form.vProb'} eq 'yes' && $counter == 0) {  
       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);  
   }  
 #  #
 # beginning of form      $request->print(<<KEYWORDS);
   if ($counter == 0) {  &nbsp;<b>Keyword Options:</b>&nbsp;
       my %keyhash = &Apache::lonnet::get  <a href="javascript:keywords(document.SCORE)"; TARGET=_self>List</a>&nbsp; &nbsp;
                 ('nohist_handgrade',[$symb.'_keywords'],  
                  $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},  
  $ENV{'course.'.$ENV{'request.course.id'}.'.num'});  
   
       $request->print('<form action="/adm/grades" method="post" name="SCORE">'."\n".  
       '<input type="hidden" name="symb"       value="'.$symb.'" />'."\n".  
       '<input type="hidden" name="url"        value="'.$url.'" />'."\n".  
       '<input type="hidden" name="vProb"      value="'.$ENV{'form.vProb'}.'" />'."\n".  
       '<input type="hidden" name="lastSub"    value="'.$last.'" />'."\n".  
       '<input type="hidden" name="section"    value="'.$ENV{'form.section'}.'">'."\n".  
       '<input type="hidden" name="submitonly" value="'.$ENV{'form.submitonly'}.'">'."\n".  
       '<input type="hidden" name="response"   value="'.$ENV{'form.response'}.'">'."\n".  
       '<input type="hidden" name="handgrade"  value="'.$ENV{'form.handgrade'}.'">'."\n".  
       '<input type="hidden" name="command"    value="handgrade" />'."\n".  
       '<input type="hidden" name="keywords"   value="'.$keyhash{$symb.'_keywords'}.'" />'."\n".  
       '<input type="hidden" name="kwclr"      value="blue" />'."\n".  
       '<input type="hidden" name="kwsize"     value="0" />'."\n".  
       '<input type="hidden" name="kwstyle"    value="" />'."\n".  
       '<input type="hidden" name="msgsub"     value="Problem title" />'."\n".  
       '<input type="hidden" name="savemsgN"   value="4" />'."\n".  
       '<input type="hidden" name="savemsg1"   value="Good Job!" />'."\n".  
       '<input type="hidden" name="savemsg2"   value="Needs a better explanation." />'."\n".  
       '<input type="hidden" name="savemsg3"   value="Read the book before submitting such garbagge!" />'."\n".  
       '<input type="hidden" name="savemsg4"   value="You are nowhere close." />'."\n".  
       '<input type="hidden" name="NCT"'.  
       ' value="'.($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1').'" />'."\n");  
   
       if ($ENV{'form.handgrade'} eq 'yes') {  
   $request->print(<<KEYWORDS);  
 &nbsp;<b>Keyword Options</b>&nbsp;  
 <a href="javascript:keywords(document.SCORE.keywords)"; TARGET=_self>Keyword List</a>&nbsp; &nbsp;  
 <a href="#" onMouseDown="javascript:getSel(); return false"  <a href="#" onMouseDown="javascript:getSel(); return false"
  CLASS="page">Paste Selection to Keyword list</a> (requires N4+ and IE5+)&nbsp; &nbsp;   CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
 <a href="javascript:kwhighlight()"; TARGET=_self>Keyword Highlight Attribute</a><br /><br />  <a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br />
 KEYWORDS  KEYWORDS
       }  
   }  
   
 #  #
 # Student info  # Load the other essays for similarity check
   $request->print(($counter == 0 ? '' : '<br /><hr><br />'));  
   my $fullname = ($ENV{'form.fullname'} ne '' ? $ENV{'form.fullname'} : &get_fullname($uname,$udom));  
   my $result.='<table border="0"><tr><td><b>Username: </b>'.$uname.  
       '</td><td><b>Fullname: </b>'.$fullname.  
 #  ($ENV{'form.fullname'} ne '' ? $ENV{'form.fullname'} : &get_fullname($uname,$udom)).  
       '</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><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              my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
   $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,      my ($adom,$aname,$apath)=($essayurl=~/^(\w+)\/(\w+)\/(.*)$/);
    $ENV{'request.course.id'},$last,      $apath=&Apache::lonnet::escape($apath);
    '.submission','Apache::grades::keywords_highlight'));      $apath=~s/\W/\_/gs;
 #      %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
 #          }
   my $wgt    = &Apache::lonnet::EXT('resource.partid.weight',$symb,$udom,$uname);      }
   my $wgtmsg = ($wgt > 0 ? '(problem weight)' : '<font color="red">problem weight assigned by computer</font>');  
   $wgt       = ($wgt > 0 ? $wgt : '1');  
   my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);  
   my $score  = ($record{'resource.0.awarded'} eq '' ? '' : $record{'resource.0.awarded'}*$wgt);  
   
 #  
 # display grading options  
   $result='<input type="hidden" name="WGT'.$counter.'" value="'.$wgt.'" />'.  
       '<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'.$udom.'" />'."\n";  
   $result.='<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n".  
   $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".  
   $result.='<table border="0"><tr><td><b>Points</b></td><td>';  
   my $ctr = 0;  
   
   $result.='<table border="0"><tr>';  # display radio buttons in a nice table with 10 across  
   while ($ctr<=$wgt) {  
       $result.= '<td><input type="radio" name="RADVAL'.$counter.'" '.  
   '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>';      if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
   $result.='<td><input type="text" name="GRADE_BOX'.$counter.'"'.   $request->print('<br /><br /><br />') if ($counter > 0);
       ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.   my $mode;
        'onChange="javascript:updateRadio(this.form.RADVAL'.$counter.   if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
         ',this.form.GRADE_BOX'.$counter.      $mode='both';
    ',this.form.GRADE_SEL'.$counter.',\''.$wgt.'\')" /></td>'."\n";   } elsif ($env{'form.vProb'} eq 'all' ) {
   $result.='<td>/'.$wgt.' '.$wgtmsg.' </td><td>';      $mode='text';
    } elsif ($env{'form.vAns'} eq 'all') {
   foreach my $part (&getpartlist($url)) {      $mode='answer';
     my ($temp,$part,$type)=split(/_/,$part);   }
     if ($type eq 'solved') {   &Apache::lonxml::clear_problem_counter();
       my ($status,$foo)=split(/_/,$record{"resource.$part.$type"},2);   $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode));
       $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";  
       $result.='<a href="javascript:msgCenter(document.SCORE,'.$counter.  
   ',\''.$fullname.'\')"; TARGET=_self>Compose Message</a></td></tr>'."\n";  
     }      }
   }  
   $result.='</table>';      my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
   $request->print($result);      my ($partlist,$handgrade,$responseType) = &response_type($symb);
 #  
 # print end of form      # Display student info
   if ($counter == $total) {      $request->print(($counter == 0 ? '' : '<br />'));
       my $endform.='<table border="0"><tr><td><input type="submit" name="gradeOpt" value="Save & Next" />';      my $result='<table border="0" width="100%"><tr><td bgcolor="#777777">'."\n".
       my $ntstu ='<select name="NTSTU">'.   '<table border="0" width="100%"><tr bgcolor="#edffff"><td>'."\n";
   '<option>1</option><option>2</option>'.  
       '<option>3</option><option>5</option>'.      $result.='<b>Fullname: </b>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'<br />'."\n";
   '<option>7</option><option>10</option></select>'."\n";      $result.='<input type="hidden" name="name'.$counter.
       my $nsel = ($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1');   '" value="'.$env{'form.fullname'}.'" />'."\n";
       $ntstu =~ s/<option>$nsel/<option selected="on">$nsel/;  
       $endform.=$ntstu.'student(s) &nbsp;&nbsp;';      # If any part of the problem is an essay-response (handgraded), then check for collaborators
       $endform.='<input type="submit" name="gradeOpt" value="Next" />&nbsp';      my @col_fullnames;
       $endform.='<input type="submit" name="gradeOpt" value="Previous" />&nbsp';      my ($classlist,$fullname);
       $endform.='(Next and Previous do not save the scores.)';      if ($env{'form.handgrade'} eq 'yes') {
       $endform.='</td><tr></table></form>';   ($classlist,undef,$fullname) = &getclasslist('all','0');
       $request->print($endform);   for (keys (%$handgrade)) {
   }      my $ncol = &Apache::lonnet::EXT('resource.'.$_.
   return '';      '.maxcollaborators',
                                               $symb,$udom,$uname);
       next if ($ncol <= 0);
               s/\_/\./g;
               next if ($record{'resource.'.$_.'.collaborators'} eq '');
               my @goodcollaborators = ();
               my @badcollaborators  = ();
       foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) { 
    $_ =~ s/[\$\^\(\)]//g;
    next if ($_ eq '');
    my ($co_name,$co_dom) = split /\@|:/,$_;
    $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 /^$co_name:$co_dom$/i,keys %$classlist;
    if (! scalar(@Matches)) {
       push @badcollaborators,$_;
    } else {
       push @goodcollaborators, @Matches;
    }
       }
               if (scalar(@goodcollaborators) != 0) {
                   $result.='<b>Collaborators: </b>';
                   foreach (@goodcollaborators) {
       my ($lastname,$givenn) = split(/,/,$$fullname{$_});
       push @col_fullnames, $givenn.' '.$lastname;
       $result.=$$fullname{$_}.'&nbsp; &nbsp; &nbsp;';
    }
                   $result.='<br />'."\n";
    my ($part)=split(/\./,$_);
    $result.='<input type="hidden" name="collaborator'.$counter.
       '" value="'.$part.':'.(join ':',@goodcollaborators).'" />'.
       "\n";
       }
       if (scalar(@badcollaborators) > 0) {
    $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>';
    $result.='This student has submitted ';
    $result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators';
    $result .= ': '.join(', ',@badcollaborators);
    $result .= '</td></tr></table>';
       }         
       if (scalar(@badcollaborators > $ncol)) {
    $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>';
    $result .= 'This student has submitted too many '.
       'collaborators.  Maximum is '.$ncol.'.';
    $result .= '</td></tr></table>';
       }
    }
       }
       $request->print($result."\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=''.
       ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '.
        $$timestamp)."</td></tr>\n";
    if ($$timestamp eq '') {
       $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0]; 
    } else {
       my %seenparts;
       for my $part (sort keys(%$handgrade)) {
    my ($partid,$respid) = split(/_/,$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.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
    $display_part.' <font color="#999999">( ID '.$respid.
    ' )</font>&nbsp; &nbsp;'.
    '<font color="red">Nothing submitted - no attempts</font><br /><br />';
       next;
    }
    foreach (@$string) {
       my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/;
       if ($part ne ($partid.'_'.$respid)) { next; }
       my ($ressub,$subval) = split(/:/,$_,2);
       # Similarity check
       my $similar='';
       if($env{'form.checkPlag'}){
    my ($oname,$odom,$ocrsid,$oessay,$osim)=
       &most_similar($uname,$udom,$subval);
    if ($osim) {
       $osim=int($osim*100.0);
       $similar="<hr /><h3><font color=\"#FF0000\">Essay".
    " is $osim% similar to an essay by ".
    &Apache::loncommon::plainname($oname,$odom).
    '</font></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} eq 'yes')) {
    my $display_part=&get_display_part($partid,$symb);
    $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
       $display_part.' <font color="#999999">( ID '.$respid.
       ' )</font>&nbsp; &nbsp;';
    my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
    if (@$files) {
       $lastsubonly.='<br /><font color="red" size="1">Like all files provided by users, this file may contain virusses</font><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>Submitted Answer: </b>'.
       &cleanRecord($subval,$responsetype,$symb,$partid,
    $respid,\%record,$order);
    if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
       }
    }
       }
    }
    $lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\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.='</td></tr></table></td></tr></table>'."\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('</td></tr></table></td></tr></table>'."\n");
       }
   
       # essay grading message center
       if ($env{'form.handgrade'} eq 'yes') {
    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> ('.
       &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" />)'.
       '<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 & Next below.').")\n";
    $request->print($result);
       }
       if ($perm{'vgr'}) {
    $request->print('<br />'.
       &Apache::loncommon::track_student_link(&mt('View recent activity'),
      $uname,$udom,'check'));
       }
       if ($perm{'opa'}) {
    $request->print('<br />'.
       &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),
    $uname,$udom,$symb,'check'));
       }
   
       my %seen = ();
       my @partlist;
       my @gradePartRespid;
       for my $part_resp (sort(keys(%$handgrade))) {
    my ($partid,$respid) = split(/_/, $part_resp);
    next if ($seen{$partid} > 0);
    $seen{$partid}++;
    next if ($$handgrade{$part_resp} =~ /:no$/ && $env{'form.lastSub'} =~ /^(hdgrade)$/);
    push @partlist,$partid;
    push @gradePartRespid,$partid.'.'.$respid;
    $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
       }
       $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.'</td></tr></table></td></tr></table>'."\n");
   
       # print end of form
       if ($counter == $total) {
    my $endform='<table border="0"><tr><td>'."\n";
    $endform.='<input type="button" value="Save & 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="on">$nsel</;
    $endform.=$ntstu.'student(s) &nbsp;&nbsp;';
    $endform.='<input type="button" value="Previous" '.
       'onClick="javascript:checksubmit(this.form,\'Previous\');" TARGET=_self> &nbsp;'."\n".
       '<input type="button" value="Next" '.
       'onClick="javascript:checksubmit(this.form,\'Next\');" TARGET=_self> &nbsp;';
    $endform.='(Next and Previous (student) do not save the scores.)'."\n" ;
    $endform.='</td><tr></table></form>';
    $endform.=&show_grading_menu_form($symb);
    $request->print($endform);
       }
       return '';
 }  }
   
   #--- Retrieve the last submission for all the parts
 sub get_last_submission {  sub get_last_submission {
   my ($symb,$username,$domain,$course)=@_;      my ($returnhash)=@_;
   if ($symb) {      my (@string,$timestamp);
      my (%returnhash)=&Apache::lonnet::restore($symb,$course,$domain,$username);      if ($$returnhash{'version'}) {
      if ($returnhash{'version'}) {  
  my %lasthash=();   my %lasthash=();
  my $version;   my ($version);
  for ($version=1;$version<=$returnhash{'version'};$version++) {   for ($version=1;$version<=$$returnhash{'version'};$version++) {
   foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) {      foreach (sort(split(/\:/,$$returnhash{$version.':keys'}))) {
       $lasthash{$_}=$returnhash{$version.':'.$_};   $lasthash{$_}=$$returnhash{$version.':'.$_};
   }     $timestamp = scalar(localtime($$returnhash{$version.':timestamp'}));
         }      }
    }
  foreach ((keys %lasthash)) {   foreach ((keys %lasthash)) {
     if ($_ =~ /\.submission$/) {return $lasthash{$_}}      if ($_ =~ /\.submission$/) {
    my ($partid,$foo) = split(/submission$/,$_);
    my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
       '<font color="red">Draft Copy</font> ' : '';
    push @string, (join(':',$_,$draft.$lasthash{$_}));
       }
  }   }
  return '';  
     }      }
   }      @string = $string[0] eq '' ? '<font color="red">Nothing submitted - no attempts.</font>' : @string;
       return \@string,\$timestamp;
 }  }
   
   #--- High light keywords, with style choosen by user.
 sub keywords_highlight {  sub keywords_highlight {
   my $string  = shift;      my $string    = shift;
   (my $url=$ENV{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;      my $size      = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
   my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));      my $styleon   = $env{'form.kwstyle'} eq ''  ? '' : $env{'form.kwstyle'};
   my %keyhash = &Apache::lonnet::get      (my $styleoff = $styleon) =~ s/\</\<\//;
       ('nohist_handgrade',[$symb.'_keywords'],      my @keylist   = split(/[,\s+]/,$env{'form.keywords'});
        $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},      foreach (@keylist) {
        $ENV{'course.'.$ENV{'request.course.id'}.'.num'});   $string =~ s/\b\Q$_\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$_$styleoff<\/font>/gi;
   my @keylist = split(/[,\s+]/,$keyhash{$symb.'_keywords'});      }
   foreach my $word (@keylist) {      return $string;
       next if ($word eq '');  
       $string =~ s/\b$word(\b|\.)/\<font color\=red\>$word\<\/font\>/gi;  
   }  
   return $string;  
 }  }
   
   #--- Called from submission routine
 sub processHandGrade {  sub processHandGrade {
   my ($request) = shift;      my ($request) = shift;
   my $url    = $ENV{'form.url'};      my $symb   = &get_symb($request);
   my $symb   = $ENV{'form.symb'};      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
   my $button = $ENV{'form.gradeOpt'};      my $button = $env{'form.gradeOpt'};
   my $ngrade = $ENV{'form.NCT'};      my $ngrade = $env{'form.NCT'};
   my $ntstu  = $ENV{'form.NTSTU'};      my $ntstu  = $env{'form.NTSTU'};
   my $keywords= $ENV{'form.keywords'};      my $cdom   = $env{'course.'.$env{'request.course.id'}.'.domain'};
   my $newmsg0  = $ENV{'form.newmsg0'};      my $cnum   = $env{'course.'.$env{'request.course.id'}.'.num'};
   print "newmsg0=$newmsg0<br>";  
   my $newmsg1  = $ENV{'form.newmsg1'};      if ($button eq 'Save & Next') {
   print "newmsg1=$newmsg1<br>";   my $ctr = 0;
   my $oldmsg1  = $ENV{'form.savemsg1'};   while ($ctr < $ngrade) {
   print "oldmsg1=$oldmsg1<br>";      my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
   my $oldmsg2  = $ENV{'form.savemsg2'};      my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr);
   print "oldmsg1=$oldmsg2<br>";      if ($errorflag eq 'no_score') {
   my $oldmsg3  = $ENV{'form.savemsg3'};   $ctr++;
   print "oldmsg1=$oldmsg3<br>";   next;
   my $oldmsg4  = $ENV{'form.savemsg4'};      }
   print "oldmsg1=$oldmsg4<br>";      if ($errorflag eq 'not_allowed') {
   my $messages  = $ENV{'form.includemsg0'};   $request->print("<font color=\"red\">Not allowed to modify grades for $uname:$udom</font>");
   print "messages=$messages<br>";   $ctr++;
  if ($keywords ne '') {   next;
       my $crsname = $ENV{'course.'.$ENV{'request.course.id'}.'.num'};      }
       my $crsdom  = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'};      my $includemsg = $env{'form.includemsg'.$ctr};
 #      my $putresult = &Apache::lonnet::put      my ($subject,$message,$msgstatus) = ('','','');
 #                ('nohist_handgrade',{$symb.'_keywords' => $keywords},      if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
 #                 $crsdom,$crsname);   $subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
   }   unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
   my (@parts) = sort(&getpartlist($url));   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;
       $message.=" for <a href=\"".
       &Apache::lonnet::clutter($url).
       "?symb=$symb\">$env{'form.probTitle'}</a>";
    }
    $msgstatus = &Apache::lonmsg::user_normal_msg($uname,$udom,
         $subject.' ['.
         &Apache::lonnet::declutter($url).']',$message);
    $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("<font color=\"red\">Not allowed to modify grades for $collaborator:$udom</font>");
       next;
    } else {
       if ($message ne '') {
    $msgstatus = &Apache::lonmsg::user_normal_msg($collaborator,$udom,$env{'form.msgsub'},$message);
       }
    }
       }
    }
       }
       $ctr++;
    }
       }
   
   if ($button eq 'Save & Next') {      if ($env{'form.handgrade'} eq 'yes') {
       my $ctr = 0;   # Keywords sorted in alphabatical order
       while ($ctr < $ENV{'form.NCT'}) {   my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
   my ($uname,$udom) = split(/:/,$ENV{'form.unamedom'.$ctr});   my %keyhash = ();
   &saveHandGrade($url,$symb,$uname,$udom,$ctr,@parts);   $env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
   if ($ENV{'form.collaborator'.$ctr}) {   $env{'form.keywords'}           =~ s/^\s+|\s+$//;
       my (@collaborators) = split(/:/,$ENV{'form.collaborator'.$ctr});   my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
       foreach (@collaborators) {   $env{'form.keywords'} = join(' ',@keywords);
   &saveHandGrade($url,$symb,$_,$udom,$ctr,@parts);   $keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
       }   $keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
   }   $keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
   $ctr++;   $keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
       }   $keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
   }  
   my $firststu = $ENV{'form.unamedom0'};   # message center - Order of message gets changed. Blank line is eliminated.
   my $laststu  = $ENV{'form.unamedom'.($ngrade-1)};   # 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 '';
       }
   
   my ($classlist) = &getclasslist($ENV{'form.section'},'0');  # 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 '';
       }
   
   my (@nextlist,@prevlist);      # Get the next/previous one or group of students
   my ($nextflg,$prevflg,$ctr,$ctprev) = (0,0,0,0);      my $firststu = $env{'form.unamedom0'};
   foreach my $student ( sort(@{ $$classlist{$ENV{'form.section'}} }) ) {      my $laststu = $env{'form.unamedom'.($ngrade-1)};
       my ($uname,$udom) = split(/:/,$student);      my $ctr = 2;
       if ($nextflg == 1 && $button =~ /Next$/) {      while ($laststu eq '') {
   push @nextlist,$uname if ($ctr < $ntstu);   $laststu  = $env{'form.unamedom'.($ngrade-$ctr)};
   $ctr++;   $ctr++;
       }   $laststu = $firststu if ($ctr > $ngrade);
       $nextflg = 1 if ($student eq $laststu);      }
       $prevflg = 1 if ($student eq $firststu);  
       if ($prevflg == 0 && $button eq 'Previous') {      my (@parsedlist,@nextlist);
   push @prevlist,$uname;      my ($nextflg) = 0;
   $ctprev++;      foreach (sort 
       }       {
   }   if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
   if ($button eq 'Previous') {       return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
       if ($ctprev <= $ntstu) {   }
   @nextlist = @prevlist;   return $a cmp $b;
       } else {       } (keys(%$fullname))) {
   my $idx = 0;   if ($nextflg == 1 && $button =~ /Next$/) {
   my $start = $ctprev - $ntstu;      push @parsedlist,$_;
   while ($idx < $ntstu) {   }
       $nextlist[$idx] = $prevlist[$start+$idx];   $nextflg = 1 if ($_ eq $laststu);
       $idx++;   if ($button eq 'Previous') {
   }      last if ($_ eq $firststu);
       }      push @parsedlist,$_;
   }   }
   $ctr = 0;      }
   my $total = scalar(@nextlist)-1;      $ctr = 0;
   foreach my $student (@nextlist) {      @parsedlist = reverse @parsedlist if ($button eq 'Previous');
       $ENV{'form.student'} = $student;      my ($partlist) = &response_type($symb);
       &submission($request,$ctr,$total);      foreach my $student (@parsedlist) {
       $ctr++;   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++;
       }
   
   return 'The End';      $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><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);
    $request->print($the_end);
       }
       return '';
 }  }
   
   #---- Save the score and award for each student, if changed
 sub saveHandGrade {  sub saveHandGrade {
   my ($url,$symb,$stuname,$domain,$newflg,@parts) = @_;      my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
   my %record=&Apache::lonnet::restore($symb,$ENV{'request.course.id'},$domain,$stuname);      my @v_flag;
   my %newrecord;      my $usec = &Apache::lonnet::getsection($domain,$stuname,
      $env{'request.course.id'});
   foreach my $part (@parts) {      if (!&canmodify($usec)) { return('not_allowed'); }
     my ($temp,$part,$type)=split(/_/,$part);      my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
     my $oldscore=$record{"resource.$part.$type"};      my @parts_graded;
     my $newscore;      my %newrecord  = ();
     if ($type eq 'awarded' && $newflg >= 0) {      my ($pts,$wgt) = ('','');
  my $pts    = ($ENV{'form.GRADE_BOX'.$newflg} ne '' ?       my %aggregate = ();
       $ENV{'form.GRADE_BOX'.$newflg} : $ENV{'form.RADVAL'.$newflg});      my $aggregateflag = 0;
  my $wgt    = $ENV{'form.WGT'.$newflg};  
 # my $sel    = $ENV{'form.GRADE_SEL'.$newflg};      my @parts = split(/:/,$env{'form.partlist'.$newflg});
  $newscore  = $pts/$wgt if ($wgt != 0);      foreach my $new_part (@parts) {
     }   #collaborator may vary for different parts
     if ($type eq 'solved') {   if ($submitter && $new_part ne $part) { next; }
       $newscore = $ENV{'form.GRADE_SEL'.$newflg} if ($newflg >= 0);   my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
       my $update=0;   if ($dropMenu eq 'excused') {
       if ($newscore eq 'nothing' ) {      if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
  if ($oldscore ne '') {   $newrecord{'resource.'.$new_part.'.solved'} = 'excused';
   $update=1;   if (exists($record{'resource.'.$new_part.'.awarded'})) {
   $newscore = '';      $newrecord{'resource.'.$new_part.'.awarded'} = '';
  }   }
       } elsif ($oldscore !~ m/^$newscore/) {      $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
  $update=1;      }
  if ($newscore eq 'correct')   { $newscore = 'correct_by_override'; }   } elsif ($dropMenu eq 'reset status'
  if ($newscore eq 'incorrect') { $newscore = 'incorrect_by_override'; }   && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
  if ($newscore eq 'excused')   { $newscore = 'excused'; }      foreach my $key (keys (%record)) {
  if ($newscore eq 'ungraded')  { $newscore = 'ungraded_attempted'; }   if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
  if ($newscore eq 'partial')   { $newscore = 'correct_partially_by_override'; }      }
       }      $newrecord{'resource.'.$new_part.'.regrader'}=
       if ($update) { $newrecord{"resource.$part.$type"}=$newscore; }   "$env{'user.name'}:$env{'user.domain'}";
     } else {              my $totaltries = $record{'resource.'.$part.'.tries'};
       if ($oldscore ne $newscore) {  
  $newrecord{"resource.$part.$type"}=$newscore;              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.
    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'}";
    }
    my ($partlist,$handgrade,$responseType) = &response_type($symb);
    foreach my $part_resp (sort(keys(%$handgrade))) {
       my ($part_id, $resp_id) = split(/_/,$part_resp);
               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;
                   while ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter}) {
                       my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'.filename'};
                       $newrecord{"resource.$new_part.$resp_id.handback"} = $env{'form.returndocorig'.$file_counter};
                       # set the filename to match the submitted file name
                       $env{'form.'.$newflg.'_'.$part_resp.'_returndoc1.filename'} = $env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter};
                       my $result=&Apache::lonnet::userfileupload($newflg.'_'.$part_resp.'_returndoc'.$file_counter,'',
            'portfolio',undef,undef,undef,$stuname,$domain);
                      if ($result !~ m|^/uploaded/|) {
                           $request->print('<font color="red"> An errror occured ('.$result.
                       ') while trying to upload '.&display_file().'</font><br />');
                   # $request->print(&done('Back'));
               }
                       $request->print("<br />".$fname." will be the uploaded file name");
                       $request->print("<font color=\"red\">Will upload document</font>".$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter});
                       $file_counter++;
                   }
               }
           }
   
    # 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 (@v_flag,$new_part);
    }
     }      }
   }      my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
   if ( scalar(keys(%newrecord)) > 0 ) {      my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
       $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";  
 #      while (my ($k,$v) = each %newrecord) {      if (scalar(keys(%newrecord)) > 0) {
 # print "k=$k:v=$v:<br>\n";          if (scalar(@v_flag)) {
 #      }              &version_portfiles(\%record, \@parts_graded, $env{'request.course.id'}, $symb, $domain, $stuname, \@v_flag);
 #      print "symb=$symb,courseid=$ENV{'request.course.id'},dom=$domain,name=$stuname<br>";          }
 #     &Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'},$domain,$stuname);   &Apache::lonnet::cstore(\%newrecord,$symb,
   }   $env{'request.course.id'},$domain,$stuname);
   return '';  
    my @ungraded_parts;
    foreach my $part (@parts) {
       if ( !defined($record{'resource.'.$part.'.awarded'})
    && !defined($newrecord{'resource.'.$part.'.awarded'}) ) {
    push(@ungraded_parts, $part);
       }
    }
    if ( !@ungraded_parts ) {
       &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,
      $cnum,$domain,$stuname);
    }
       }
       if ($aggregateflag) {
           &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
         $cdom,$cnum);
       }
       return ('',$pts,$wgt);
 }  }
   
 sub get_symb_and_url {  sub get_submitted_files {
   my ($request) = @_;      my ($udom,$uname,$partid,$respid,$record) = @_;
   (my $url=$ENV{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;      my @files;
   my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));      if ($$record{"resource.$partid.$respid.portfiles"}) {
   if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }          my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
   return ($symb,$url);          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);
 }  }
   
 sub show_grading_menu_form {  # ----------- Provides number of tries since last reset.
   my ($symb,$url)=@_;  sub get_num_tries {
   my $result.='<form action="/adm/grades" method="post">'."\n".      my ($record,$last_reset,$part) = @_;
     '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".      my $timestamp = '';
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".      my $num_tries = 0;
  '<input type="hidden" name="command" value="gradingmenu" />'."\n".      if ($$record{'version'}) {
   '<input type="submit" name="submit" value="Grading Menu" />'."\n".          for (my $version=$$record{'version'};$version>=1;$version--) {
     '</form>'."\n";              if (exists($$record{$version.':resource.'.$part.'.solved'})) {
   return $result;                  $timestamp = $$record{$version.':timestamp'};
                   if ($timestamp > $last_reset) {
                       $num_tries ++;
                   } else {
                       last;
                   }
               }
           }
       }
       return $num_tries;
 }  }
   
 sub gradingmenu {  # ----------- Determine decrements required in aggregate totals 
   my ($request) = @_;  sub decrement_aggs {
   my ($symb,$url)=&get_symb_and_url($request);      my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
   if (!$symb) {return '';}      my %decrement = (
   my $allkeys = &Apache::lonnet::metadata($url,'keys');                          attempts => 0,
   my $handgrade = ($allkeys =~ /parameter_.*?_handgrade/ ? 'yes' : 'no');                          users => 0,
   my ($responsetype,$foo) = split(/_/,&Apache::lonnet::metadata($url,'packages'));                          correct => 0
                       );
   my $result='<h2>&nbsp;<font color="#339933">Select a Grading Method</font></h2>';      $decrement{'attempts'} = $aggtries;
   $result.='<table border="0">';      if ($solvedstatus =~ /^correct/) {
   $result.='<tr><td><font size=+1><b>Resource: </b></font></td>'.          $decrement{'correct'} = 1;
       '<td><font size=+1>'.$url.'</font></td></tr>';      }
   $result.='<tr><td><font size=+1><b>Type: </b></font></td>'.      if ($aggtries == $totaltries) {
       '<td><font size=+1>'.$responsetype.'&nbsp;&nbsp;<b>Handgrade: </b>'.$handgrade.'</font></td></tr>';          $decrement{'users'} = 1;
   $result.='</table>';      }
       foreach my $type (keys (%decrement)) {
   $result.=&view_edit_entire_class_form($symb,$url).'<br />';          $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
   $result.=&upcsvScores_form($symb,$url).'<br />';      }
   $result.=&viewGradeaStu_form($symb,$url,$responsetype,$handgrade).'<br />';      return;
   $result.=&verifyReceipt_form($symb,$url);  
   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 {  # ----------- Determine timestamps for last reset of aggregate totals for parts  
   my ($symb,$url) = @_;  sub get_last_resets {
   if (!$symb) {return '';}      my ($symb,$courseid,$partids) =@_;
   my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";      my %last_resets;
   $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";      my $cdom = $env{'course.'.$courseid.'.domain'};
   $result.='&nbsp;<b>Specify a file containing the class scores for above resource</b></td></tr>'."\n";      my $cname = $env{'course.'.$courseid.'.num'};
   $result.='<tr bgcolor=#ffffe6><td>'."\n";      my @keys;
   my $upfile_select=&Apache::loncommon::upfile_select_html();      foreach my $part (@{$partids}) {
   $result.=<<ENDUPFORM;   push(@keys,"$symb\0$part\0resettime");
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">      }
 <input type="hidden" name="symb" value="$symb" />      my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys,
 <input type="hidden" name="url" value="$url" />       $cdom,$cname);
 <input type="hidden" name="command" value="csvuploadmap" />      foreach my $part (@{$partids}) {
 $upfile_select   $last_resets{$part}=$results{"$symb\0$part\0resettime"};
 <br />&nbsp;<input type="submit" name="submit" value="Upload Grades" />      }
 </form>      return %last_resets;
 ENDUPFORM  }
   $result.='</td></tr></table>'."\n";  
   $result.='</td></tr></table>'."\n";  # ----------- Handles creating versions for portfolio files as answers
   return $result;  sub version_portfiles {
 }      my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_;
       my $version_parts = join('|',@$v_flag);
 sub viewGradeaStu_form {      my $parts = join('|', @$parts_graded);
   my ($symb,$url,$response,$handgrade) = @_;      my $portfolio_root = &Apache::loncommon::propath($domain,
   my ($classlist,$sections) = &getclasslist('all','0');   $stu_name).
   my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";   '/userfiles/portfolio';
   $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";      foreach my $key (keys(%$record)) {
   $result.='&nbsp;<b>View/Grade an Individual Student\'s Submission</b></td></tr>'."\n";          my $new_portfiles;
   $result.='<tr bgcolor=#ffffe6><td>'."\n";          if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
   $result.='<form action="/adm/grades" method="post">'."\n".              my @v_portfiles;
      '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".              my @portfiles = split(/,/,$$record{$key});
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".              foreach my $file (@portfiles) {
        '<input type="hidden" name="response" value="'.$response.'" />'."\n".                  &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
         '<input type="hidden" name="handgrade" value="'.$handgrade.'" />'."\n".                  my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
  '<input type="hidden" name="command" value="submission" />'."\n";                  my $version = 0;
    my ($answer_name,$answer_ver,$answer_ext) =
   $result.='&nbsp;<b>Select section:</b> <select name="section">'."\n";      &file_name_version_ext($answer_file);
   foreach my $section (sort (@$sections)) {                  my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);
       $result.= '<option>'.$section.'</option>'."\n";                  $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
   }                  my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
   $result.= '<option selected="on">all</select>'."\n";                  if ($new_answer ne 'problem getting file') {
   $result.='&nbsp;&nbsp;<b>Display students who has: </b>'.                      push(@v_portfiles, $directory.$new_answer);
       '<input type="radio" name="submitonly" value="yes" checked> submitted'.                      &Apache::lonnet::mark_as_readonly($domain,$stu_name,
   '<input type="radio" name="submitonly" value="all"> everybody <br />';                          ['/portfolio'.$directory.$new_answer],
   $result.='&nbsp;(Section "no" implies the students were not assigned a section.)<br />'                           [$symb,$env{'request.course.id'},'graded']);
       if (grep /no/,@$sections);                  }
                   
   $result.='<br />&nbsp;<input type="submit" name="submit" value="View/Grade" />'."\n".              }
       '</form>'."\n";              $$record{$key} = join(',',@v_portfiles);
   $result.='</td></tr></table>'."\n";          }
   $result.='</td></tr></table>'."\n";      } 
   return $result;      return 'ok';   
 }  }
   
 sub verifyReceipt_form {  sub get_next_version {
   my ($symb,$url) = @_;      my ($answer_name, $answer_ext, $dir_list);
   my $cdom=$ENV{"course.$ENV{'request.course.id'}.domain"};      my $version;
   my $cnum=$ENV{"course.$ENV{'request.course.id'}.num"};      foreach my $row (@$dir_list) {
   my $hostver=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'});          my ($file) = split(/\&/,$row,2);
           my ($file_name,$file_version,$file_ext) =
   my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";      &file_name_version_ext($file);
   $result.='<table width=100% border=0><tr><td bgcolor=#e6ffff>'."\n";          if (($file_name eq $answer_name) && 
   $result.='&nbsp;<b>Verify a Submission Receipt Issued by this Server</td></tr>'."\n";      ($file_ext eq $answer_ext)) {
   $result.='<tr bgcolor=#ffffe6><td>'."\n";                  # gets here if filename and extension match, regardless of version
   $result.='<form action="/adm/grades" method="post">'."\n";                  if ($file_version ne '') {
   $result.='&nbsp;<tt>'.$hostver.'-<input type="text" name="receipt" size="4"></tt><br />'."\n";                  # a versioned file is found  so save it for later
   $result.='&nbsp;<input type="submit" name="submit" value="Verify Receipt">'."\n";                  if ($file_version > $version) {
   $result.='<input type="hidden" name="command" value="verify">'."\n";      $version = $file_version;
   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'}.'" />';      $version ++;
   }      return($version);
   $result.='</form>';  }
   $result.='</td></tr></table>'."\n";  
   $result.='</td></tr></table>'."\n";  sub version_selected_portfile {
   return $result;      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') {
           &Apache::lonnet::logthis('problem getting file '.$file_name);
           $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 {  sub viewgrades {
   my ($request) = @_;      my ($request) = shift;
   my $result='';      &viewgrades_js($request);
   
   #get resource reference      my ($symb) = &get_symb($request);
   my ($symb,$url)=&get_symb_and_url($request);      #need to make sure we have the correct data for later EXT calls, 
   if (!$symb) {return '';}      #thus invalidate the cache
   #get classlist      &Apache::lonnet::devalidatecourseresdata(
   my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'});                   $env{'course.'.$env{'request.course.id'}.'.num'},
   #print "Found $cdom:$cnum<br />";                   $env{'course.'.$env{'request.course.id'}.'.domain'});
   my ($classlist) = &getclasslist('all','0');      &Apache::lonnet::clear_EXT_cache_status();
   my $headerclr = '"#ccffff"';  
   my $cellclr = '"#ffffcc"';      my $result='<h3><font color="#339933">'.&mt('Manual Grading').'</font></h3>';
       $result.='<font size=+1><b>Current Resource: </b>'.$env{'form.probTitle'}.'</font>'."\n";
   #get list of parts for this problem  
   my (@parts) = sort(&getpartlist($url));      #view individual student submission form - called using Javascript viewOneStudent
       $result.=&jscriptNform($symb);
   $request->print ("<h2><font color=\"#339933\">Manual Grading</font></h2>");  
       #beginning of class grading form
   #start the form      $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
   $result = '<form action="/adm/grades" method="post">'."\n".   '<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
     '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".  
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".  
  '<input type="hidden" name="command" value="editgrades" />'."\n".   '<input type="hidden" name="command" value="editgrades" />'."\n".
   '<input type="submit" name="submit" value="Submit Changes" />'."\n".   '<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n".
     '<table border=0><tr><td bgcolor="#777777">'."\n".   '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
      '<table border=0>'."\n".   '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n".
       '<tr bgcolor='.$headerclr.'><td><b>Username</b></td><td><b>Name</b></td><td><b>Domain</b></td>'."\n";   '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
   foreach my $part (@parts) {  
      my $display=&Apache::lonnet::metadata($url,$part.'.display');      my $sectionClass;
      if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }      if ($env{'form.section'} eq 'all') {
      $result.='<td><b>'.$display.'</b></td>'."\n";   $sectionClass='Class </h3>';
   }      } elsif ($env{'form.section'} eq 'none') {
   $result.='</tr>';   $sectionClass='Students in no Section </h3>';
   #get info for each student      } else {
   foreach my $student ( sort(@{ $$classlist{'all'} }) ) {   $sectionClass='Students in Section '.$env{'form.section'}.'</h3>';
     my $display=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},$student,@parts);      }
 #      print "ID=$ENV{'request.course.id'}:STU=$student:DIS=$display:<br>\n";      $result.='<h3>Assign Common Grade To '.$sectionClass;
     $result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},$student,@parts);      $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
   }   '<table border=0><tr bgcolor="#ffffdd"><td>';
   $result.='</table></td></tr></table>';      #radio buttons/text box for assigning points for a section or class.
   $result.='<input type="submit" name="submit" value="Submit Changes" /></form>';      #handles different parts of a problem
   $result.=&show_grading_menu_form($symb,$url);      my ($partlist,$handgrade) = &response_type($symb);
   return $result;      my %weight = ();
       my $ctsparts = 0;
       $result.='<table border="0">';
       my %seen = ();
       for (sort keys(%$handgrade)) {
    my ($partid,$respid) = split (/_/,$_,2);
    next if $seen{$partid};
    $seen{$partid}++;
    my $handgrade=$$handgrade{$_};
    my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
    $weight{$partid} = $wgt eq '' ? '1' : $wgt;
   
    $result.='<input type="hidden" name="partid_'.
       $ctsparts.'" value="'.$partid.'" />'."\n";
    $result.='<input type="hidden" name="weight_'.
       $partid.'" value="'.$weight{$partid}.'" />'."\n";
    my $display_part=&get_display_part($partid,$symb);
    $result.='<tr><td><b>Part:</b> '.$display_part.'&nbsp; &nbsp;<b>Point:</b> </td><td>';
    $result.='<table border="0"><tr>';  
    my $ctr = 0;
    while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
       $result.= '<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++;
    }
    $result.='</tr></table>';
    $result.= '</td><td><b> or </b><input type="text" name="TEXTVAL_'.
       $partid.'" size="4" '.'onChange="javascript:writePoint(\''.
    $partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
       $weight{$partid}.' (problem weight)</td>'."\n";
    $result.= '</td><td><select name="SELVAL_'.$partid.'"'.
       'onChange="javascript:writeRadText(\''.$partid.'\','.
    $weight{$partid}.')"> '.
       '<option selected="on"> </option>'.
       '<option>excused</option>'.
       '<option>reset status</option></select></td>'.
               '<td><label><input type="checkbox" name="FORCE_'.$partid.'" /> Override "Correct"</label></td></tr>'."\n";
    $ctsparts++;
       }
       $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n".
    '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
       $result.='<input type="button" value="Reset" '.
    'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self>';
   
       #table listing all the students in a section/class
       #header of table
       $result.= '<h3>Assign Grade to Specific Students in '.$sectionClass;
       $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
    '<table border=0><tr bgcolor="#deffff"><td>&nbsp;<b>No.</b>&nbsp;</td>'.
    '<td>'.&nameUserString('header')."</td>\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.='<td><b>Score Part:</b> '.$display_part.
    ' <br /><b>(weight = '.$weight{$partid}.')</b></td>'."\n";
       next;
    } else {
       $display =~s/\[Part: \Q$partid\E\]/Part:<\/b> $display_part/;
    }
    $display =~ s|Problem Status|Grade Status<br />|;
    $result.='<td><b>'.$display.'</td>'."\n";
       }
       $result.='</tr>';
   
       my %last_resets = 
    &get_last_resets($symb,$env{'request.course.id'},\@partids);
   
       #get info for each student
       #list all the students - with points and grade status
       my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
       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.='</table></td></tr></table>';
       $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
       $result.='<input type="button" value="Save" '.
    'onClick="javascript:submit();" TARGET=_self /></form>'."\n";
       if (scalar(%$fullname) eq 0) {
    my $colspan=3+scalar(@parts);
    $result='<font color="red">There are no students in section "'.$env{'form.section'}.
       '" with enrollment status "'.$env{'form.Status'}.'" to modify or grade.</font>';
       }
       $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='<tr bgcolor="#ffffdd"><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> '.
    '<font color="#999999">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</font></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="on">excused</option>' 
    : '<option selected="on"> </option><option>excused</option>')."\n";
       $result.='<option>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.='</tr>';
       return $result;
 }  }
   
   #--- change scores for all the students in a section/class
   #    record does not get update if unchanged
 sub editgrades {  sub editgrades {
   my ($request) = @_;      my ($request) = @_;
   my $result='';  
   
   my $symb=$ENV{'form.symb'};      my $symb=&get_symb($request);
   if ($symb eq '') { $request->print("Unable to handle ambiguous references:$symb:$ENV{'form.url'}"); return ''; }      my $title='<h3><font color="#339933">Current Grade Status</font></h3>';
   my $url=$ENV{'form.url'};      $title.='<font size=+1><b>Current Resource: </b>'.$env{'form.probTitle'}.'</font><br />'."\n";
   #get classlist      $title.='<font size=+1><b>Section: </b>'.$env{'form.section'}.'</font>'."\n";
 #  my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'});  
   #print "Found $cdom:$cnum<br />";      my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";
   my ($classlist) = &getclasslist('all','0');      $result.= '<table border="0"><tr bgcolor="#deffff">'.
    '<td rowspan=2 valign="center">&nbsp;<b>No.</b>&nbsp;</td>'.
   #get list of parts for this problem   '<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n";
   my (@parts) = &getpartlist($url);  
       my %scoreptr = (
   $result.='<form action="/adm/grades" method="post">'."\n".      'correct'  =>'correct_by_override',
     '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".      'incorrect'=>'incorrect_by_override',
       '<input type="hidden" name="url" value="'.$url.'" />'."\n".      'excused'  =>'excused',
  '<input type="hidden" name="command" value="viewgrades" />'."\n".      'ungraded' =>'ungraded_attempted',
   '<input type="submit" name="submit" value="See Grades" /> <br />'."\n";      '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 .= '<td align="center">&nbsp;<b>Old Score</b>&nbsp;</td>'.
       '<td align="center">&nbsp;<b>New Score</b>&nbsp;</td>';
    $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 .= '<td align="center">&nbsp;<b>Old '.$display.'</b>&nbsp;</td>'.
    '<td align="center">&nbsp;<b>New '.$display.'</b>&nbsp;</td>';
       $columns{$partid}+=2;
    }
       }
       foreach my $partid (@partid) {
    my $display_part=&get_display_part($partid,$symb);
    $result .= '<td colspan="'.$columns{$partid}.
       '" align="center"><b>Part:</b> '.$display_part.
       ' (Weight = '.$weight{$partid}.')</td>';
   
   foreach my $student ( sort(@{ $$classlist{'all'} }) ) {      }
     $result.=&setstudentgrade($url,$symb,$ENV{'request.course.id'},$student,@parts);      $result .= '</tr><tr bgcolor="#deffff">';
   }      $result .= $header;
       $result .= '</tr>'."\n";
       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;
       $noupdate.=$line."<td colspan=\"$numcols\"><font color=\"red\">Not allowed to modify student</font></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.='</tr>'."\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.='<input type="submit" name="submit" value="See Grades" /></table></form>';      $result.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line;
   return $result;      $updateCtr++;
    } else {
       $noupdate.='<tr bgcolor="#ffffde"><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 .= '<tr bgcolor="#ffffff"><td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td></tr><tr bgcolor="#ffffde">'.$noupdate;
       }
       $result .= '</table></td></tr></table>'."\n".
    &show_grading_menu_form ($symb);
       my $msg = '<br /><b>Number of records updated = '.$rec_update.
    ' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'.
    '<b>Total number of students = '.$env{'form.total'}.'</b><br />';
       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 1225  ENDPICK Line 3226  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 1260  ENDPICK Line 3263  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');
       $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><font color="#339933">Uploading Class Grades</font></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 1322  sub csvuploadmap_footer { Line 3337  sub csvuploadmap_footer {
 ENDPICK  ENDPICK
 }  }
   
   sub checkforfile_js {
       my $result =<<CSVFORMJS;
   <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>
   CSVFORMJS
       return $result;
   }
   
   sub upcsvScores_form {
       my ($request) = shift;
       my ($symb)=&get_symb($request);
       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>Specify a file containing the class scores for current resource'.
    '.</b></td></tr>'."\n";
       $result.='<tr bgcolor=#ffffe6><td>'."\n";
       my $upfile_select=&Apache::loncommon::upfile_select_html();
       my $ignore=&mt('Ignore First Line');
       $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 Scores" />
   <label><input type="checkbox" name="noFirstLine" />$ignore</label>
   </form>
   ENDUPFORM
       $result.='</td></tr></table>'."\n";
       $result.='</td></tr></table><br /><br />'."\n";
       $result.=&show_grading_menu_form($symb);
       return $result;
   }
   
   
 sub csvuploadmap {  sub csvuploadmap {
   my ($request)= @_;      my ($request)= @_;
   my ($symb,$url)=&get_symb_and_url($request);      my ($symb)=&get_symb($request);
   if (!$symb) {return '';}      if (!$symb) {return '';}
   my $datatoken;  
   if (!$ENV{'form.datatoken'}) {      my $datatoken;
     $datatoken=&Apache::loncommon::upfile_store($request);      if (!$env{'form.datatoken'}) {
   } else {   $datatoken=&Apache::loncommon::upfile_store($request);
     $datatoken=$ENV{'form.datatoken'};      } else {
     &Apache::loncommon::load_tmp_file($request);   $datatoken=$env{'form.datatoken'};
   }   &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)));  
     }      }
   }      my @records=&Apache::loncommon::upfile_record_sep();
   &csvuploadmap_footer($request,$i,$keyfields);      if ($env{'form.noFirstLine'}) { shift(@records); }
   return '';      &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
       my ($i,$keyfields);
       if (@records) {
    my @fields=&csvupload_fields($symb);
   
    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);
               foreach my $rec (@records) {
                   my %temp = &Apache::loncommon::record_sep($rec);
                   if (%temp) {
                       $keyfields=join(',',sort(keys(%temp)));
                       last;
                   }
               }
    }
       }
       &csvuploadmap_footer($request,$i,$keyfields);
       $request->print(&show_grading_menu_form($symb));
   
       return '';
   }
   
   sub csvuploadoptions {
       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><font color="#339933">Uploading Class Grade Options</font></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 '';
   }
   
   sub get_fields {
       my %fields;
       my @keyfields = split(/\,/,$env{'form.keyfields'});
       for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
    if ($env{'form.upfile_associate'} eq 'reverse') {
       if ($env{'form.f'.$i} ne 'none') {
    $fields{$keyfields[$i]}=$env{'form.f'.$i};
       }
    } else {
       if ($env{'form.f'.$i} ne 'none') {
    $fields{$env{'form.f'.$i}}=$keyfields[$i];
       }
    }
       }
       return %fields;
 }  }
   
 sub csvuploadassign {  sub csvuploadassign {
   my ($request)= @_;      my ($request)= @_;
   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);      &Apache::loncommon::load_tmp_file($request);
   my @gradedata=&Apache::loncommon::upfile_record_sep();      my @gradedata = &Apache::loncommon::upfile_record_sep();
   my @keyfields = split(/\,/,$ENV{'form.keyfields'});      if ($env{'form.noFirstLine'}) { shift(@gradedata); }
   my %fields=();      my %fields=&get_fields();
   for (my $i=0; $i<=$ENV{'form.nfields'}; $i++) {      $request->print('<h3>Assigning Grades</h3>');
     if ($ENV{'form.upfile_associate'} eq 'reverse') {      my $courseid=$env{'request.course.id'};
       if ($ENV{'form.f'.$i} ne 'none') {      my ($classlist) = &getclasslist('all',0);
  $fields{$keyfields[$i]}=$ENV{'form.f'.$i};      my @notallowed;
       }      my @skipped;
     } else {      my $countdone=0;
       if ($ENV{'form.f'.$i} ne 'none') {      foreach my $grade (@gradedata) {
  $fields{$ENV{'form.f'.$i}}=$keyfields[$i];   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);
    $entries{$fields{$dest}}=~s/\s//g;
    my $pcr=$entries{$fields{$dest}} / $wgt;
    my $award='correct_by_override';
    $grades{"resource.$part.awarded"}=$pcr;
    $grades{"resource.$part.solved"}=$award;
    $points{$part}=1;
       } 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 store"); }
    $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
   # &Apache::lonnet::logthis(" storing ".(join('-',%grades)));
    my $result=&Apache::lonnet::cstore(\%grades,$symb,
      $env{'request.course.id'},
      $domain,$username);
    if ($result eq 'ok') {
       $request->print('.');
    } else {
       $request->print("<p>
                                 <font color='red'>
                                    Failed to store student $username\@$domain.
                                    Message when trying to store was ($result)
                                 </font>
                                </p>" );
    }
    $request->rflush();
    $countdone++;
     }      }
   }      $request->print("<br />Stored $countdone students\n");
   $request->print('<h3>Assigning Grades</h3>');      if (@skipped) {
   my $courseid=$ENV{'request.course.id'};   $request->print('<p><font size="+1"><b>Skipped Students</b></font></p>');
 #  my $cdom=$ENV{"course.$courseid.domain"};   foreach my $student (@skipped) { $request->print("$student<br />\n"); }
 #  my $cnum=$ENV{"course.$courseid.num"};      }
   my ($classlist) = &getclasslist('all','1');      if (@notallowed) {
   my @skipped;   $request->print('<p><font size="+1" color="red"><b>Students Not Allowed to Modify</b></font></p>');
   my $countdone=0;   foreach my $student (@notallowed) { $request->print("$student<br />\n"); }
   foreach my $grade (@gradedata) {      }
     my %entries=&Apache::loncommon::record_sep($grade);      $request->print("<br />\n");
     my $username=$entries{$fields{'username'}};      $request->print(&show_grading_menu_form($symb));
     my $domain=$entries{$fields{'domain'}};      return '';
     if (!exists($$classlist{"$username:$domain"})) {  
       push(@skipped,"$username:$domain");  
       next;  
     }  
     my %grades;  
     foreach my $dest (keys(%fields)) {  
       if ($dest eq 'username' || $dest eq 'domain') { next; }  
       if ($entries{$fields{$dest}} eq '') { next; }  
       my $store_key=$dest;  
       $store_key=~s/^stores/resource/;  
       $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'},  
     $domain,$username);  
     $request->print('.');  
     $request->rflush();  
     $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 '';  
 }  }
   #------------- 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;
   
 sub send_header {      $request->print(<<LISTJAVASCRIPT);
   my ($request)= @_;  <script type="text/javascript" language="javascript">
   $request->print(&Apache::lontexconvert::header());  
 #  $request->print("  function checkPickOne(formname) {
 #<script>      if (radioSelection(formname.student) == null) {
 #remotewindow=open('','homeworkremote');   alert("Please select the student you wish to grade.");
 #remotewindow.close();   return;
 #</script>");       }
   $request->print('<body bgcolor="#FFFFFF">');      ptr = pullDownSelection(formname.selectpage);
       formname.page.value = formname["page"+ptr].value;
       formname.title.value = formname["title"+ptr].value;
       formname.submit();
 }  }
   
 sub send_footer {  </script>
   my ($request)= @_;  LISTJAVASCRIPT
   $request->print('</body>');      &commonJSfunctions($request);
   $request->print(&Apache::lontexconvert::footer());      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><font color="#339933">&nbsp;'.
    'Manual Grading by Page or Sequence</font></h3>';
   
       $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
       $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";
       my ($titles,$symbx) = &getSymbMap($request);
       my ($curpage) =&Apache::lonnet::decode_symb($symb); 
   #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
   #    my $type=($curpage =~ /\.(page|sequence)/);
       my $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $result.='<option value="'.$ctr.'" '.
       ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : '').
       '>'.$showtitle.'</option>'."\n";
    $ctr++;
       }
       $result.= '</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";
   
       $result.='&nbsp;<b>View Problems Text: </b><label><input type="radio" name="vProb" value="no" checked="on" /> no </label>'."\n".
    '<label><input type="radio" name="vProb" value="yes" /> yes </label>'."<br />\n";
   
       $result.='&nbsp;<b>Submission Details: </b>'.
    '<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n".
    '<label><input type="radio" name="lastSub" value="datesub" checked /> by dates and submissions</label>'."\n".
    '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n";
   
       $result.='<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".
    '<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
    '<input type="hidden" name="command" value="displayPage" />'."\n".
    '<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
    '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
   
       $result.='&nbsp;<input type="button" '.
    'onClick="javascript:checkPickOne(this.form);"value="Next->" /><br />'."\n";
   
       $request->print($result);
   
       my $studentTable.='&nbsp;<b>Select a student you wish to grade and then click on the Next button.</b><br />'.
    '<table border="0"><tr><td bgcolor="#777777">'.
    '<table border="0"><tr bgcolor="#e6ffff">'.
    '<td align="right">&nbsp;<b>No.</b></td>'.
    '<td>'.&nameUserString('header').'</td>'.
    '<td align="right">&nbsp;<b>No.</b></td>'.
    '<td>'.&nameUserString('header').'</td></tr>';
    
       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 ? '<tr bgcolor="#ffffe6">' : '</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></tr>' : '');
    $ptr++;
       }
       $studentTable.='</td><td>&nbsp;</td><td>&nbsp;' if ($ptr%2 == 0);
       $studentTable.='</td></tr></table></td></tr></table>'."\n";
       $studentTable.='<input type="button" '.
    'onClick="javascript:checkPickOne(this.form);"value="Next->" /></form>'."\n";
   
       $studentTable.=&show_grading_menu_form($symb);
       $request->print($studentTable);
   
       return '';
 }  }
   
 sub handler {  sub getSymbMap {
   my $request=$_[0];      my ($request) = @_;
       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.'.'.$sequence->compTitle();
       push @titles, $title; # minder in case two titles are identical
       $symbx{$title} = $sequence->symb();
       $minder++;
    }
       }
       return \@titles,\%symbx;
   }
   
   if ($ENV{'browser.mathml'}) {  #
     $request->content_type('text/xml');  #--- Displays a page/sequence w/wo problems, w/wo submissions
   } else {  sub displayPage {
     $request->content_type('text/html');      my ($request) = shift;
   }  
   $request->send_http_header;      my ($symb) = &get_symb($request);
   return OK if $request->header_only;      my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
   &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});      my $cnum      = $env{"course.$env{'request.course.id'}.num"};
   my $url=$ENV{'form.url'};      my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
   my $symb=$ENV{'form.symb'};      my $pageTitle = $env{'form.page'};
   my $command=$ENV{'form.command'};      my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
   if (!$url) {      my ($uname,$udom) = split(/:/,$env{'form.student'});
     my ($temp1,$temp2);      my $usec=$classlist->{$env{'form.student'}}[5];
     ($temp1,$temp2,$ENV{'form.url'})=split(/___/,$symb);  
     $url = $ENV{'form.url'};      #need to make sure we have the correct data for later EXT calls, 
   }      #thus invalidate the cache
   &send_header($request);      &Apache::lonnet::devalidatecourseresdata(
   if ($url eq '' && $symb eq '') {                   $env{'course.'.$env{'request.course.id'}.'.num'},
      if ($ENV{'user.adv'}) {                   $env{'course.'.$env{'request.course.id'}.'.domain'});
          if (($ENV{'form.codeone'}) && ($ENV{'form.codetwo'}) &&      &Apache::lonnet::clear_EXT_cache_status();
              ($ENV{'form.codethree'})) {  
              my $token=$ENV{'form.codeone'}.'*'.$ENV{'form.codetwo'}.'*'.      if (!&canview($usec)) {
         $ENV{'form.codethree'};   $request->print('<font color="red">Unable to view requested student.('.$env{'form.student'}.')</font>');
              my ($tsymb,$tuname,$tudom,$tcrsid)=   $request->print(&show_grading_menu_form($symb));
  &Apache::lonnet::checkin($token);   return;
              if ($tsymb) {      }
                 my ($map,$id,$url)=split(/\_\_\_/,$tsymb);      my $result='<h3><font color="#339933">&nbsp;'.$env{'form.title'}.'</font></h3>';
                 if (&Apache::lonnet::allowed('mgr',$tcrsid)) {      $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom).
                    $request->print(   '</h3>'."\n";
                      &Apache::lonnet::ssi('/res/'.$url,      &sub_page_js($request);
                         ('grade_username' => $tuname,      $request->print($result);
                          'grade_domain' => $tudom,  
                          'grade_courseid' => $tcrsid,      my $navmap = Apache::lonnavmaps::navmap->new();
                          'grade_symb' => $tsymb)));      my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
                 } else {      my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
                    $request->print('<h1>Not authorized: '.$token.'</h1>');      if (!$map) {
                 }              $request->print('<font color="red">Unable to view requested sequence. ('.$resUrl.')</font>');
    $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="'.$symb.'" />'."\n".
    '<input type="hidden" name="overRideScore" value="no" />'."\n".
    '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
   
       my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL').
    '/check.gif" height="16" border="0" />';
   
       $studentTable.='&nbsp;<b>Note:</b> Problems graded correct by the computer are marked with a '.$checkIcon.
    ' symbol.'."\n".
    '<table border="0"><tr><td bgcolor="#777777">'.
    '<table border="0"><tr bgcolor="#e6ffff">'.
    '<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.
    '<td><b>&nbsp;'.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade</b></td></tr>';
   
       &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() && !$curRes->randomout) {
       my $parts = $curRes->parts();
               my $title = $curRes->compTitle();
       my $symbx = $curRes->symb();
       $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).'&nbsp;parts)').'</td>';
       $studentTable.='<td valign="top">';
       if ($env{'form.vProb'} eq 'yes' ) {
    $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
        undef,'both');
     } 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'});
    $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;<b>Correct answer:</b><br />'.$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;<font color="red">No recorded submission for this problem</font><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.='</td></tr></table></td></tr></table>'."\n".
    '<input type="button" value="Save" '.
    'onClick="javascript:checkSubmitPage(this.form,'.$question.');" TARGET=_self />'.
    '</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='<table border="0" width="100%"><tr><td bgcolor="#777777">'.
    '<table border="0" width="100%"><tr bgcolor="#e6ffff">'.
    '<td><b>Date/Time</b></td>'.
    ($isCODE?'<td><b>CODE</b></td>':'').
    '<td><b>Submission</b></td>'.
    '<td><b>Status&nbsp;</b></td></tr>';
       my ($version);
       my %mark;
       my %orders;
       $mark{'correct_by_student'} = $checkIcon;
       if (!exists($$record{'1:timestamp'})) {
    return '<br />&nbsp;<font color="red">Nothing submitted - no attempts</font><br />';
       }
   
       my $interaction;
       for ($version=1;$version<=$$record{'version'};$version++) {
    my $timestamp = scalar(localtime($$record{$version.':timestamp'}));
    if (exists($$record{$version.':resource.0.version'})) {
       $interaction = $$record{$version.':resource.0.version'};
    }
   
    my $where = ($isTask ? "$version:resource.$interaction"
                : "$version:resource");
    #&Apache::lonnet::logthis(" got $where");
    $studentTable.='<tr bgcolor="#ffffff" valign="top"><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$/));
       #&Apache::lonnet::logthis("match $matchKey $responseId (".$$record{$version.':'.$matchKey});
       $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';
       $displaySub[0].='<font color="#999999">(ID&nbsp;'.
    $responseId.')</font>&nbsp;<b>';
       if ($$record{"$where.$partid.tries"} eq '') {
    $displaySub[0].='Trial&nbsp;not&nbsp;counted';
       } else {
    $displaySub[0].='Trial&nbsp;'.
       $$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].='Checked in by '.
       $$record{"$where.$partid.checkedin"}.' into slot '.
       $$record{"$where.$partid.checkedin.slot"}.
       '<br />';
       }
       if (exists $$record{"$where.$partid.award"}) {
    $displaySub[1].='<b>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.='Manually graded by '.$displaySub[2];
    }
    $studentTable.='&nbsp;</td></tr>';
       
       }
       $studentTable.='</table></td></tr></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('<font color="red">Unable to modify requested student.('.$env{'form.student'}.'</font>');
    $request->print(&show_grading_menu_form($env{'form.symb'}));
    return;
       }
       my $result='<h3><font color="#339933">&nbsp;'.$env{'form.title'}.'</font></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('<font color="red">Unable to grade requested sequence. ('.$resUrl.')</font>');
    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='<table border="0"><tr><td bgcolor="#777777">'.
    '<table border="0"><tr bgcolor="#e6ffff">'.
    '<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.
    '<td><b>&nbsp;Title&nbsp;</b></td>'.
    '<td><b>&nbsp;Previous Score&nbsp;</b></td>'.
    '<td><b>&nbsp;New Score&nbsp;</b></td></tr>';
   
       $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() && !$curRes->randomout) {
       my $parts = $curRes->parts();
               my $title = $curRes->compTitle();
       my $symbx = $curRes->symb();
       $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).'&nbsp;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 == $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) {
    &Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
    $udom,$uname);
       }
               if ($aggregateflag) {
                   &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
                         $env{'course.'.$env{'request.course.id'}.'.domain'},
                         $env{'course.'.$env{'request.course.id'}.'.num'});
             }              }
  } else {  
              $request->print(&Apache::lonxml::tokeninputfield());      $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
          }   '<td valign="top">'.$displayPts[1].'</td>'.
      }   '</tr>';
   } 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.='</td></tr></table></td></tr></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') {  sub defaultFormData {
       if ($ENV{'form.associate'} ne 'Reverse Association') {      my ($symb)=@_;
  $request->print(&csvuploadassign($request));      return '
       } else {        <input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
  if ( $ENV{'form.upfile_associate'} ne 'reverse' ) {       '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
   $ENV{'form.upfile_associate'} = 'reverse';       '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
   }
   
   sub getSequenceDropDown {
       my ($request,$symb)=@_;
       my $result='<select name="selectpage">'."\n";
       my ($titles,$symbx) = &getSymbMap($request);
       my ($curpage)=&Apache::lonnet::decode_symb($symb); 
       my $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $result.='<option value="'.$$symbx{$_}.'" '.
       ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : '').
       '>'.$showtitle.'</option>'."\n";
    $ctr++;
       }
       $result.= '</select>';
       return $result;
   }
   
   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,
       &Apache::loncommon::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;
   }
   
   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="on"':'').">$filename</option>\n";
       }
       $result.="</select>";
       return $result;
   }
   
   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;
   }
   
   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;
   }
   
   sub scantron_CODEunique {
       my $result='<nobr>
                    <label><input type="radio" name="scantron_CODEunique"
                           value="yes" checked="checked" /> Yes </label>
                   </nobr>
                   <nobr>
                    <label><input type="radio" name="scantron_CODEunique"
                           value="no" /> No </label>
                   </nobr>';
       return $result;
   }
   
   sub scantron_selectphase {
       my ($r,$file2grade) = @_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $sequence_selector=&getSequenceDropDown($r,$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;
       #FIXME allow instructor to be able to download the scantron file
       # and to upload it,
       $result.= <<SCANTRONFORM;
       <table width="100%" border="0">
       <tr>
        <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
         <td bgcolor="#777777">
          <input type="hidden" name="command" value="scantron_warning" />
           $default_form_data
           <table width="100%" border="0">
             <tr bgcolor="#e6ffff">
               <td colspan="2">
                 &nbsp;<b>Specify file and which Folder/Sequence to grade</b>
               </td>
             </tr>
             <tr bgcolor="#ffffe6">
               <td> Sequence to grade: </td><td> $sequence_selector </td>
             </tr>
             <tr bgcolor="#ffffe6">
               <td> Filename of scoring office file: </td><td> $file_selector </td>
             </tr>
             <tr bgcolor="#ffffe6">
               <td> Format of data file: </td><td> $format_selector </td>
             </tr>
             <tr bgcolor="#ffffe6">
               <td> Saved CODEs to validate against: </td><td> $CODE_selector</td>
             </tr>
             <tr bgcolor="#ffffe6">
               <td> Each CODE is only to be used once:</td><td> $CODE_unique </td>
             </tr>
             <tr bgcolor="#ffffe6">
       <td> Options: </td>
               <td>
          <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records</label> <br />
                  <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all exisiting corrections</label> <br />
                  <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label>
       </td>
             </tr>
             <tr bgcolor="#ffffe6">
               <td colspan="2">
                 <input type="submit" value="Grading: Validate Scantron Records" />
               </td>
             </tr>
           </table>
          </td>
        </form>
       </tr>
   SCANTRONFORM
      
       $r->print($result);
   
       if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
           &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
   
           $r->print(<<SCANTRONFORM);
       <tr>
         <td bgcolor="#777777">
           <table width="100%" border="0">
             <tr bgcolor="#e6ffff">
               <td>
                 &nbsp;<b>Specify a Scantron data file to upload.</b>
               </td>
             </tr>
             <tr bgcolor="#ffffe6">
               <td>
   SCANTRONFORM
       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(<<SCANTRONFORM);
               </td>
             </tr>
           </table>
         </td>
       </tr>
   SCANTRONFORM
       }
       $r->print(<<SCANTRONFORM);
       <tr>
         <form action='/adm/grades' name='scantron_download'>
           <td bgcolor="#777777">
             <input type="hidden" name="command" value="scantron_download" />
             <table width="100%" border="0">
               <tr bgcolor="#e6ffff">
                 <td colspan="2">
                   &nbsp;<b>Download a scoring office file</b>
                 </td>
               </tr>
               <tr bgcolor="#ffffe6">
                 <td> Filename of scoring office file: </td><td> $file_selector </td>
               </tr>
               <tr bgcolor="#ffffe6">
                 <td colspan="2">
                   <input type="submit" value="Download: Show List of Associated Files" />
                 </td>
               </tr>
             </table>
           </td>
         </form>
       </tr>
   SCANTRONFORM
   
       $r->print(<<SCANTRONFORM);
     </table>
   $grading_menu_button
   SCANTRONFORM
   
       return
   }
   
   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;
   }
   
   sub username_to_idmap {
       my ($classlist)= @_;
       my %idmap;
       foreach my $student (keys(%$classlist)) {
    $idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}=
       $student;
       }
       return %idmap;
   }
   
   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') {
    my $length=$scantron_config->{'Qlength'};
    my $off=$scantron_config->{'Qoff'};
    my $on=$scantron_config->{'Qon'};
    my $answer=${off}x$length;
    if ($args->{'response'} eq 'none') {
       &scan_data($scan_data,
          "$whichline.no_bubble.".$args->{'question'},'1');
  } else {   } else {
   $ENV{'form.upfile_associate'} = 'forward';      if ($on eq 'letter') {
    my @alphabet=('A'..'Z');
    $answer=$alphabet[$args->{'response'}];
       } elsif ($on eq 'number') {
    $answer=$args->{'response'}+1;
       } else {
    substr($answer,$args->{'response'},1)=$on;
       }
       &scan_data($scan_data,
          "$whichline.no_bubble.".$args->{'question'},undef,'1');
  }   }
  $request->print(&csvuploadmap($request));   my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
       }   substr($line,$where-1,$length)=$answer;
       }
       return $line;
   }
   
   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};
   }
   
   sub scantron_parse_scanline {
       my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_;
       my %record;
       my $questions=substr($line,$$scantron_config{'Qstart'}-1);
       my $data=substr($line,0,$$scantron_config{'Qstart'}-1);
       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 {
       #FIXME interpret first N questions
    }
       }
       $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 ($justHeader) { return \%record; }
   
       my @alphabet=('A'..'Z');
       my $questnum=0;
       while ($questions) {
    $questnum++;
    my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});
    substr($questions,0,$$scantron_config{'Qlength'})='';
    if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }
    if ($$scantron_config{'Qon'} eq 'letter') {
       if ($currentquest eq '?') {
    push(@{$record{'scantron.doubleerror'}},$questnum);
    $record{"scantron.$questnum.answer"}='';
       } elsif (!$currentquest 
        || $currentquest eq $$scantron_config{'Qoff'}
        || $currentquest !~ /^[A-Z]$/) {
    $record{"scantron.$questnum.answer"}='';
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
    }
       } else {
    $record{"scantron.$questnum.answer"}=$currentquest;
       }
    } elsif ($$scantron_config{'Qon'} eq 'number') {
       if ($currentquest eq '?') {
    push(@{$record{'scantron.doubleerror'}},$questnum);
    $record{"scantron.$questnum.answer"}='';
    } elsif (!$currentquest 
    || $currentquest eq $$scantron_config{'Qoff'} 
    || $currentquest !~ /^\d$/) {
    $record{"scantron.$questnum.answer"}='';
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
    }
       } else {
    $record{"scantron.$questnum.answer"}=
       $alphabet[$currentquest-1];
       }
    } else {
       my @array=split($$scantron_config{'Qon'},$currentquest,-1);
       if (length($array[0]) eq $$scantron_config{'Qlength'}) {
    $record{"scantron.$questnum.answer"}='';
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
    }
       } else {
    $record{"scantron.$questnum.answer"}=
       $alphabet[length($array[0])];
       }
       if (scalar(@array) gt 2) {
    push(@{$record{'scantron.doubleerror'}},$questnum);
    my @ans=@array;
    my $i=length($ans[0]);shift(@ans);
    while ($#ans) {
       $i+=length($ans[0])+1;
       $record{"scantron.$questnum.answer"}.=$alphabet[$i];
       shift(@ans);
    }
       }
    }
       }
       $record{'scantron.maxquest'}=$questnum;
       return \%record;
   }
   
   sub scantron_add_delay {
       my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
       push(@$delayqueue,
    {'line' => $scanline, 'emsg' => $errormessage,
     'ecode' => $errorcode }
    );
   }
   
   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;
   }
   
   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;
   }
   
   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("<font color='red'>Unable to accept last correction, an error occurred :$errmsg:</font>");
     } else {      } else {
       $request->print("Unknown action: $command:");   &scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
    &scantron_putfile($scanlines,$scan_data);
     }      }
   }  }
   &send_footer($request);  
   return OK;  sub reset_skipping_status {
       my ($scanlines,$scan_data)=&scantron_getfile();
       &scan_data($scan_data,'remember_skipping',undef,1);
       &scantron_putfile(undef,$scan_data);
   }
   
   sub allow_skipping {
       my ($scan_data,$i)=@_;
       my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
       delete($remembered{$i});
       &scan_data($scan_data,'remember_skipping',join(':',%remembered));
   }
   
   sub should_be_skipped {
       my ($scan_data,$i)=@_;
       if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
    # not redoing old skips
    return 0;
       }
       my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
       if (exists($remembered{$i})) { return 0; }
       return 1;
   }
   
   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);
   }
   
   sub check_for_error {
       my ($r,$result)=@_;
       if ($result ne 'ok' && $result ne 'not_found' ) {
    $r->print("An error occured ($result) when trying to Remove the existing corrections.");
       }
   }
   
   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="a";
       if ($scantron_config{'CODElocation'} &&
    $scantron_config{'CODEstart'} &&
    $scantron_config{'CODElength'}) {
    $CODElist=$env{'form.scantron_CODElist'};
    if ($CODElist eq '') { $CODElist='<font color="red">None</font>'; }
    $CODElist=
       '<tr><td><b>List of CODES to validate against:</b></td><td><tt>'.
       $CODElist.'</tt></td></tr>';
       }
       return (<<STUFF);
   <p>
   <font color="red">Please double check the information
                    below before clicking on '$button_text'</font>
   </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>
   </font>
   <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
   }
   
   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><font color="red">You have not selected a Sequence to grade</font></p>');
    } 
    if ( $env{'form.scantron_selectfile'} eq '') {
       $r->print('<p><font color="red">You have not selected a file that contains the student\'s response data.</font></p>');
    } 
    if ( $env{'form.scantron_format'} eq '') {
       $r->print('<p><font color="red">You have not selected a the format of the student\'s response data.</font></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)."</body></html>");
       return '';
   }
   
   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
       return $result;
   }
   
   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();
    &scantron_remove_file('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 neccessary 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).
         "</body></html>");
       return '';
   }
   
   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);
   }
   
   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;
   }
   
   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);
   }
   
   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);
   
   }
   
   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);
   }
   
   sub scantron_get_line {
       my ($scanlines,$scan_data,$i)=@_;
       if (&should_be_skipped($scan_data,$i)) { return undef; }
       if ($scanlines->{'skipped'}[$i]) { return undef; }
       if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
       return $scanlines->{'orig'}[$i]; 
   }
   
   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;
   }
   
   sub scantron_put_line {
       my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
       if ($skip) {
    $scanlines->{'skipped'}[$i]=$newline;
    &allow_skipping($scan_data,$i);
    return;
       }
       $scanlines->{'corrected'}[$i]=$newline;
   }
   
   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;
   }
   
   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 resource in the sequece currently are not set to exam mode. Grading these resources currently may not work correctly.')."</p>");
       return (1,$currentphase);
    }
       }
   
       return (0,$currentphase+1);
   }
   
   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();
   
       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);
   }
   
   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, probaly 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="on" '; }
       $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.</label><input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
       $r->print("\n<br />");
       $i++;
    }
       }
    }
    if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
       my $checked; if (!$i) { $checked=' checked="on" '; }
       $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.</label>");
       $r->print("\n<br />");
    }
   
    $r->print(<<ENDSCRIPT);
   <script type="text/javascript">
   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=".&Apache::lonnet::escape("scantronupload").
      "&scantron_format=".&Apache::lonnet::escape($env{'form.scantron_format'}).
      "&scantron_CODElist=".&Apache::lonnet::escape($env{'form.scantron_CODElist'}).
      "&curCODE=".&Apache::lonnet::escape($$scan_record{'scantron.CODE'}).
      "&scantron_selectfile=".&Apache::lonnet::escape($env{'form.scantron_selectfile'});
    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=$$scan_record{"scantron.$question.answer"};
       &scantron_bubble_selector($r,$scan_config,$question,split('',$selected));
    }
       } 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=$$scan_record{"scantron.$question.answer"};
       &scantron_bubble_selector($r,$scan_config,$question);
    }
       } else {
    $r->print("\n<ul>");
       }
       $r->print("\n</li></ul>");
   
   }
   
   sub scantron_bubble_selector {
       my ($r,$scan_config,$quest,@selected)=@_;
       my $max=$$scan_config{'Qlength'};
   
       my $scmode=$$scan_config{'Qon'};
       if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }     
   
       my @alphabet=('A'..'Z');
       $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>");
       for (my $i=0;$i<$max+1;$i++) {
    $r->print("\n".'<td align="center">');
    if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
    else { $r->print('&nbsp;'); }
    $r->print('</td>');
       }
       $r->print('</tr><tr>');
       for (my $i=0;$i<$max;$i++) {
    $r->print("\n".
     '<td><label><input type="radio" name="scantron_correct_Q_'.
     $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
       }
       $r->print('<td><label><input type="radio" name="scantron_correct_Q_'.
         $quest.'" value="none" /> No bubble </label></td>');
       $r->print('</tr></table>');
   }
   
   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;
   }
   
   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]);
   }
   
   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;
   }
   
   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();
   
       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);
   }
   
   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();
       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);
   }
   
   sub scantron_get_maxbubble {    
       if (defined($env{'form.scantron_maxbubble'}) &&
    $env{'form.scantron_maxbubble'}) {
    return $env{'form.scantron_maxbubble'};
       }
   
       my $navmap=Apache::lonnavmaps::navmap->new();
       my (undef,undef,$sequence)=
    &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
       my $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
   
       &Apache::lonxml::clear_problem_counter();
   
       foreach my $resource (@resources) {
    my $result=&Apache::lonnet::ssi($resource->src(),
    ('symb' => $resource->symb()));
       }
       &Apache::lonnet::delenv('scantron\.');
       $env{'form.scantron_maxbubble'} =
    &Apache::lonxml::get_problem_counter()-1;
   
       return $env{'form.scantron_maxbubble'};
   }
   
   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;
    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);
   }
   
   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);
       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);
   
    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'}) &&
    $scan_record->{'scantron.CODE'}) {
    $form{'CODE'}=$scan_record->{'scantron.CODE'};
       } else {
    $form{'CODE'}='';
       }
       my $result=&Apache::lonnet::ssi($resource->src(),%form);
       if ($result ne '') {
    &Apache::lonnet::logthis("scantron grading error -> $result");
    &Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $env{'request.course.id'} url ".$resource->src());
       }
       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 '';
   }
   
   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 '';
   }
   
   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("<font color='red'>Error:</font> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.");
       } else {
    my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);
    if ($result =~ m|^/uploaded/|) {
       $r->print("<font color='green'>Success:</font> Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");
    } else {
       $r->print("<font color='red'>Error:</font> An error (".$result.") 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 '';
   }
   
   sub valid_file {
       my ($requested_file)=@_;
       foreach my $filename (sort(&scantron_filenames())) {
    if ($requested_file eq $filename) { return 1; }
       }
       return 0;
   }
   
   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 '';
   }
   
   #-------- 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="'.$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="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;
   }
   
   #--- Displays the main menu page -------
   sub gradingmenu {
       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 $result='<h3>&nbsp;<font color="#339933">Manual Grading/View Submission</font></h3>';
       my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
       $result.=$table;
       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="'.$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.='<table width="100%" border="0"><tr><td bgcolor=#777777>'."\n".
    '<table width="100%" border="0"><tr bgcolor="#e6ffff"><td colspan="2">'."\n".
    '&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".
    '<tr bgcolor="#ffffe6" valign="top"><td>'."\n";
   
       $result.='<table width="100%" border="0">';
       $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".
    '&nbsp;'.&mt('Select Section').': <select name="section">'."\n";
       if (ref($sections)) {
    foreach (sort (@$sections)) {
       $result.='<option value="'.$_.'" '.
    ($saveSec eq $_ ? 'selected="on"':'').'>'.$_.'</option>'."\n";
    }
       }
       $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="on"' : ''). '>all</option></select> &nbsp; ';
   
       $result.=&mt('Student Status').':</b>'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef);
   
       $result.='</td></tr>';
   
       $result.='<tr bgcolor="#ffffe6"valign="top"><td><label>'.
    '<input type="radio" name="radioChoice" value="submission" '.
    ($saveCmd eq 'submission' ? 'checked' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').
    '</label> <select name="submitonly">'.
    '<option value="yes" '.
    ($saveSub eq 'yes' ? 'selected="on"' : '').' />'.&mt('with submissions').'</option>'.
    '<option value="queued" '.
    ($saveSub eq 'queued' ? 'selected="on"' : '').' />'.&mt('in grading queue').'</option>'.
    '<option value="graded" '.
    ($saveSub eq 'graded' ? 'selected="on"' : '').' />'.&mt('with ungraded submissions').'</option>'.
    '<option value="incorrect" '.
    ($saveSub eq 'incorrect' ? 'selected="on"' : '').' />'.&mt('with incorrect submissions').'</option>'.
    '<option value="all" '.
    ($saveSub eq 'all' ? 'selected="on"' : '').' />'.&mt('with any status').'</option></select></td></tr>'."\n";
   
       $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
    '<label><input type="radio" name="radioChoice" value="viewgrades" '.
    ($saveCmd eq 'viewgrades' ? 'checked' : '').' /> '.
    '<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n";
   
       $result.='<tr bgcolor="#ffffe6" valign="top"><td>'.
    '<label><input type="radio" name="radioChoice" value="pickStudentPage" '.
    ($saveCmd eq 'pickStudentPage' ? 'checked' : '').' /> '.
    'The <b>complete</b> set/page/sequence: For one student</label></td></tr>'."\n";
   
       $result.='<tr bgcolor="#ffffe6"><td><br />'.
    '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.
    '</td></tr></table>'."\n";
   
       $result.='</td><td valign="top">';
   
       $result.='<table width="100%" border="0">';
       $result.='<tr bgcolor="#ffffe6"><td>'.
    '<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.
    ' '.&mt('scores from file').' </td></tr>'."\n";
   
       $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
    '<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'.
    '" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n";
   
       if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) {
    $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
       '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'.
       ' '.&mt('receipt').': '.
       &Apache::lonnet::recprefix($env{'request.course.id'}).
       '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'.
       '</td></tr>'."\n";
       } 
       $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
    '<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'.
    '" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n";
       $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
    '<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'.
    '" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n";
   
       $result.='</form></td></tr></table>'."\n".
    '</td></tr></table>'."\n".
    '</td></tr></table>'."\n";
       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 handler {
       my $request=$_[0];
   
       &reset_perm();
       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));
       }
       &send_header($request);
       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(&gradingmenu($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 '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)");
    }
       }
       &send_footer($request);
       return '';
   }
   
   sub send_header {
       my ($request)= @_;
       $request->print(&Apache::lontexconvert::header());
   #  $request->print("
   #<script>
   #remotewindow=open('','homeworkremote');
   #remotewindow.close();
   #</script>"); 
       $request->print(&Apache::loncommon::bodytag('Grading'));
       $request->rflush();
   }
   
   sub send_footer {
       my ($request)= @_;
       $request->print('</body></html>');
 }  }
   
 1;  1;

Removed from v.1.36  
changed lines
  Added in v.1.336


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