Diff for /loncom/homework/grades.pm between versions 1.460 and 1.574

version 1.460, 2007/10/17 09:40:49 version 1.574, 2009/05/14 14:24:07
Line 26 Line 26
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
   
   
   
 package Apache::grades;  package Apache::grades;
 use strict;  use strict;
 use Apache::style;  use Apache::style;
Line 47  use LONCAPA; Line 49  use LONCAPA;
 use POSIX qw(floor);  use POSIX qw(floor);
   
   
 my %perm=();  
 my %bubble_lines_per_response = ();     # no. bubble lines for each response.  
                                    # index is "symb.part_id"  
   
 my %first_bubble_line = (); # First bubble line no. for each bubble.  
   
 # Save and restore the bubble lines array to the form env.  
   
   
 sub save_bubble_lines {  
     foreach my $line (keys(%bubble_lines_per_response)) {  
  $env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};  
  $env{"form.scantron.first_bubble_line.$line"} =  
     $first_bubble_line{$line};  
     }  
 }  
   
   
 sub restore_bubble_lines {  
     my $line = 0;  
     %bubble_lines_per_response = ();  
     while ($env{"form.scantron.bubblelines.$line"}) {  
  my $value = $env{"form.scantron.bubblelines.$line"};  
  $bubble_lines_per_response{$line} = $value;  
  $first_bubble_line{$line}  =  
     $env{"form.scantron.first_bubble_line.$line"};  
  $line++;  
     }  
   
 }  my %perm=();
   
 #  Given the parsed scanline, get the response for   #  These variables are used to recover from ssi errors
 #  'answer' number n:  
   
 sub get_response_bubbles {  
     my ($parsed_line, $response)  = @_;  
   
   my $ssi_retries = 5;
   my $ssi_error;
   my $ssi_error_resource;
   my $ssi_error_message;
   
     my $bubble_line = $first_bubble_line{$response-1} +1;  
     my $bubble_lines= $bubble_lines_per_response{$response-1};  
       
     my $selected = "";  
   
     for (my $bline = 0; $bline < $bubble_lines; $bline++) {  sub ssi_with_retries {
  $selected .= $$parsed_line{"scantron.$bubble_line.answer"};      my ($resource, $retries, %form) = @_;
  $bubble_line++;      my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
       if ($response->is_error) {
    $ssi_error          = 1;
    $ssi_error_resource = $resource;
    $ssi_error_message  = $response->code . " " . $response->message;
     }      }
     return $selected;  
 }  
   
       return $content;
   
 # ----- These first few routines are general use routines.----  
   
 # Return the number of occurences of a pattern in a string.  
   
 sub occurence_count {  
     my ($string, $pattern) = @_;  
   
     my @matches = ($string =~ /$pattern/g);  
   
     return scalar(@matches);  
 }  }
   #
   #  Prodcuces an ssi retry failure error message to the user:
   #
   
   sub ssi_print_error {
 # Take a string known to have digits and convert all the      my ($r) = @_;
 # digits into letters in the range J,A..I.      my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
       $r->print('
 sub digits_to_letters {  <br />
     my ($input) = @_;  <h2>'.&mt('An unrecoverable network error occurred:').'</h2>
   <p>
     my @alphabet = ('J', 'A'..'I');  '.&mt('Unable to retrieve a resource from a server:').'<br />
   '.&mt('Resource:').' '.$ssi_error_resource.'<br />
     my @input    = split(//, $input);  '.&mt('Error:').' '.$ssi_error_message.'
     my $output ='';  </p>
     for (my $i = 0; $i < scalar(@input); $i++) {  <p>'.
  if ($input[$i] =~ /\d/) {  &mt('It is recommended that you try again later, as this error may mean the server was just temporarily unavailable, or is down for maintenance.').'<br />'.
     $output .= $alphabet[$input[$i]];  &mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
  } else {  '</p>');
     $output .= $input[$i];      return;
  }  
     }  
     return $output;  
 }  }
   
 #  #
Line 171  sub get_symb { Line 134  sub get_symb {
 sub nameUserString {  sub nameUserString {
     my ($type,$fullname,$uname,$udom) = @_;      my ($type,$fullname,$uname,$udom) = @_;
     if ($type eq 'header') {      if ($type eq 'header') {
  return '<b>&nbsp;Fullname&nbsp;</b><span class="LC_internal_info">(Username)</span>';   return '<b>&nbsp;'.&mt('Fullname').'&nbsp;</b><span class="LC_internal_info">('.&mt('Username').')</span>';
     } else {      } else {
  return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.   return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
     ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';      ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
Line 239  sub showResourceInfo { Line 202  sub showResourceInfo {
     my %resptype = ();      my %resptype = ();
     my $hdgrade='no';      my $hdgrade='no';
     my %partsseen;      my %partsseen;
     foreach my $partID (sort keys(%$responseType)) {      foreach my $partID (sort(keys(%$responseType))) {
  foreach my $resID (sort keys(%{ $responseType->{$partID} })) {   foreach my $resID (sort(keys(%{ $responseType->{$partID} }))) {
     my $handgrade=$$handgrade{$partID.'_'.$resID};      my $handgrade=$$handgrade{$partID.'_'.$resID};
     my $responsetype = $responseType->{$partID}->{$resID};      my $responsetype = $responseType->{$partID}->{$resID};
     $hdgrade = $handgrade if ($handgrade eq 'yes');      $hdgrade = $handgrade if ($handgrade eq 'yes');
Line 254  sub showResourceInfo { Line 217  sub showResourceInfo {
  $partsseen{$partID}=1;   $partsseen{$partID}=1;
     }      }
     my $display_part=&get_display_part($partID,$symb);      my $display_part=&get_display_part($partID,$symb);
     $result.='<td><b>Part: </b>'.$display_part.' <span class="LC_internal_info">'.      $result.='<td><b>'.&mt('Part').': </b>'.$display_part.
  $resID.'</span></td>'.                  ' <span class="LC_internal_info">'.$resID.'</span></td>'.
  '<td><b>Type: </b>'.$responsetype.'</td></tr>';   '<td><b>'.&mt('Type').': </b>'.$responsetype.'</td></tr>';
 #    '<td><b>Handgrade: </b>'.$handgrade.'</td></tr>';  #    '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>';
  }   }
     }      }
     $result.='</table>'."\n";      $result.='</table>'."\n";
Line 271  sub reset_caches { Line 234  sub reset_caches {
   
 {  {
     my %analyze_cache;      my %analyze_cache;
       my %analyze_cache_formkeys;
   
     sub reset_analyze_cache {      sub reset_analyze_cache {
  undef(%analyze_cache);   undef(%analyze_cache);
           undef(%analyze_cache_formkeys);
     }      }
   
     sub get_analyze {      sub get_analyze {
  my ($symb,$uname,$udom)=@_;   my ($symb,$uname,$udom,$no_increment,$add_to_hash)=@_;
  my $key = "$symb\0$uname\0$udom";   my $key = "$symb\0$uname\0$udom";
  return $analyze_cache{$key} if (exists($analyze_cache{$key}));   if (exists($analyze_cache{$key})) {
               my $getupdate = 0;
               if (ref($add_to_hash) eq 'HASH') {
                   foreach my $item (keys(%{$add_to_hash})) {
                       if (ref($analyze_cache_formkeys{$key}) eq 'HASH') {
                           if (!exists($analyze_cache_formkeys{$key}{$item})) {
                               $getupdate = 1;
                               last;
                           }
                       } else {
                           $getupdate = 1;
                       }
                   }
               }
               if (!$getupdate) {
                   return $analyze_cache{$key};
               }
           }
   
  my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);   my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
  $url=&Apache::lonnet::clutter($url);   $url=&Apache::lonnet::clutter($url);
  my $subresult=&Apache::lonnet::ssi($url,          my %form = ('grade_target'      => 'analyze',
    ('grade_target' => 'analyze'),                      'grade_domain'      => $udom,
    ('grade_domain' => $udom),                      'grade_symb'        => $symb,
    ('grade_symb' => $symb),                      'grade_courseid'    =>  $env{'request.course.id'},
    ('grade_courseid' =>                       'grade_username'    => $uname,
     $env{'request.course.id'}),                      'grade_noincrement' => $no_increment);
    ('grade_username' => $uname));          if (ref($add_to_hash)) {
               %form = (%form,%{$add_to_hash});
           } 
    my $subresult=&ssi_with_retries($url, $ssi_retries,%form);
  (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);   (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
  my %analyze=&Apache::lonnet::str2hash($subresult);   my %analyze=&Apache::lonnet::str2hash($subresult);
           if (ref($add_to_hash) eq 'HASH') {
               $analyze_cache_formkeys{$key} = $add_to_hash;
           } else {
               $analyze_cache_formkeys{$key} = {};
           }
  return $analyze_cache{$key} = \%analyze;   return $analyze_cache{$key} = \%analyze;
     }      }
   
     sub get_order {      sub get_order {
  my ($partid,$respid,$symb,$uname,$udom)=@_;   my ($partid,$respid,$symb,$uname,$udom,$no_increment)=@_;
  my $analyze = &get_analyze($symb,$uname,$udom);   my $analyze = &get_analyze($symb,$uname,$udom,$no_increment);
  return $analyze->{"$partid.$respid.shown"};   return $analyze->{"$partid.$respid.shown"};
     }      }
   
     sub get_radiobutton_correct_foil {      sub get_radiobutton_correct_foil {
  my ($partid,$respid,$symb,$uname,$udom)=@_;   my ($partid,$respid,$symb,$uname,$udom)=@_;
  my $analyze = &get_analyze($symb,$uname,$udom);   my $analyze = &get_analyze($symb,$uname,$udom);
  foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) {          my $foils = &get_order($partid,$respid,$symb,$uname,$udom);
     if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {          if (ref($foils) eq 'ARRAY') {
  return $foil;      foreach my $foil (@{$foils}) {
           if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
       return $foil;
           }
     }      }
  }   }
     }      }
   
       sub scantron_partids_tograde {
           my ($resource,$cid,$uname,$udom,$check_for_randomlist) = @_;
           my (%analysis,@parts);
           if (ref($resource)) {
               my $symb = $resource->symb();
               my $add_to_form;
               if ($check_for_randomlist) {
                   $add_to_form = { 'check_parts_withrandomlist' => 1,};
               }
               my $analyze = &get_analyze($symb,$uname,$udom,undef,$add_to_form);
               if (ref($analyze) eq 'HASH') {
                   %analysis = %{$analyze};
               }
               if (ref($analysis{'parts'}) eq 'ARRAY') {
                   foreach my $part (@{$analysis{'parts'}}) {
                       my ($id,$respid) = split(/\./,$part);
                       if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) {
                           push(@parts,$part);
                       }
                   }
               }
           }
           return (\%analysis,\@parts);
       }
   
 }  }
   
 #--- Clean response type for display  #--- Clean response type for display
Line 332  sub cleanRecord { Line 351  sub cleanRecord {
     $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';      $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  }   }
  return '<blockquote><table border="1">'.   return '<blockquote><table border="1">'.
     '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.      '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
     $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';      $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
     } elsif ($response eq 'match') {      } elsif ($response eq 'match') {
  my %answer=&Apache::lonnet::str2hash($answer);   my %answer=&Apache::lonnet::str2hash($answer);
Line 352  sub cleanRecord { Line 371  sub cleanRecord {
     $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';      $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  }   }
  return '<blockquote><table border="1">'.   return '<blockquote><table border="1">'.
     '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.      '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Item ID</span></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
     $middlerow.'</tr>'.      $middlerow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
     $bottomrow.'</tr>'.'</table></blockquote>';      $bottomrow.'</tr>'.'</table></blockquote>';
     } elsif ($response eq 'radiobutton') {      } elsif ($response eq 'radiobutton') {
  my %answer=&Apache::lonnet::str2hash($answer);   my %answer=&Apache::lonnet::str2hash($answer);
Line 365  sub cleanRecord { Line 384  sub cleanRecord {
  foreach my $foil (@$order) {   foreach my $foil (@$order) {
     if (exists($answer{$foil})) {      if (exists($answer{$foil})) {
  if ($foil eq $correct) {   if ($foil eq $correct) {
     $toprow.='<td><b>true</b></td>';      $toprow.='<td><b>'.&mt('true').'</b></td>';
  } else {   } else {
     $toprow.='<td><i>true</i></td>';      $toprow.='<td><i>'.&mt('true').'</i></td>';
  }   }
     } else {      } else {
  $toprow.='<td>false</td>';   $toprow.='<td>'.&mt('false').'</td>';
     }      }
     $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';      $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  }   }
  return '<blockquote><table border="1">'.   return '<blockquote><table border="1">'.
     '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.      '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
     $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';      $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
     } elsif ($response eq 'essay') {      } elsif ($response eq 'essay') {
  if (! exists ($env{'form.'.$symb})) {   if (! exists ($env{'form.'.$symb})) {
Line 733  sub verifyreceipt { Line 752  sub verifyreceipt {
     $receipt     =~ s/[^\-\d]//g;      $receipt     =~ s/[^\-\d]//g;
     my ($symb)   = &get_symb($request);      my ($symb)   = &get_symb($request);
   
     my $title.='<h3><span class="LC_info">Verifying Submission Receipt '.      my $title.=
  $receipt.'</h3></span>'."\n".   '<h3><span class="LC_info">'.
  '<h4><b>Resource: </b>'.$env{'form.probTitle'}.'</h4><br /><br />'."\n";   &mt('Verifying  Receipt No. [_1]',$receipt).
    '</span></h3>'."\n".
    '<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}).
    '</h4>'."\n";
   
     my ($string,$contents,$matches) = ('','',0);      my ($string,$contents,$matches) = ('','',0);
     my (undef,undef,$fullname) = &getclasslist('all','0');      my (undef,undef,$fullname) = &getclasslist('all','0');
Line 745  sub verifyreceipt { Line 767  sub verifyreceipt {
  $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }   $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
     my $parts=['0'];      my $parts=['0'];
     if ($receiptparts) { ($parts)=&response_type($symb); }      if ($receiptparts) { ($parts)=&response_type($symb); }
       
       my $header = 
    &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th>&nbsp;'.&mt('Fullname').'&nbsp;</th>'."\n".
    '<th>&nbsp;'.&mt('Username').'&nbsp;</th>'."\n".
    '<th>&nbsp;'.&mt('Domain').'&nbsp;</th>';
       if ($receiptparts) {
    $header.='<th>&nbsp;'.&mt('Problem Part').'&nbsp;</th>';
       }
       $header.=
    &Apache::loncommon::end_data_table_header_row();
   
     foreach (sort       foreach (sort 
      {       {
  if (lc($$fullname{$a}) ne lc($$fullname{$b})) {   if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
Line 755  sub verifyreceipt { Line 790  sub verifyreceipt {
  my ($uname,$udom)=split(/\:/);   my ($uname,$udom)=split(/\:/);
  foreach my $part (@$parts) {   foreach my $part (@$parts) {
     if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {      if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
  $contents.='<tr bgcolor="#ffffe6"><td>&nbsp;'."\n".   $contents.=
       &Apache::loncommon::start_data_table_row().
       '<td>&nbsp;'."\n".
     '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.      '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
     '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".      '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
     '<td>&nbsp;'.$uname.'&nbsp;</td>'.      '<td>&nbsp;'.$uname.'&nbsp;</td>'.
Line 763  sub verifyreceipt { Line 800  sub verifyreceipt {
  if ($receiptparts) {   if ($receiptparts) {
     $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';      $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
  }   }
  $contents.='</tr>'."\n";   $contents.= 
       &Apache::loncommon::end_data_table_row()."\n";
   
  $matches++;   $matches++;
     }      }
  }   }
     }      }
     if ($matches == 0) {      if ($matches == 0) {
  $string = $title.'No match found for the above receipt.';   $string = $title.&mt('No match found for the above receipt.');
     } else {      } else {
  $string = &jscriptNform($symb).$title.   $string = &jscriptNform($symb).$title.
     'The above receipt matches the following student'.      '<p>'.
     ($matches <= 1 ? '.' : 's.')."\n".      &mt('The above receipt matches the following [numerate,_1,student].',$matches).
     '<table border="0"><tr><td bgcolor="#777777">'."\n".      '</p>'.
     '<table border="0"><tr bgcolor="#e6ffff">'."\n".      $header.
     '<td><b>&nbsp;Fullname&nbsp;</b></td>'."\n".      $contents.
     '<td><b>&nbsp;Username&nbsp;</b></td>'."\n".      &Apache::loncommon::end_data_table()."\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);      return $string.&show_grading_menu_form($symb);
 }  }
Line 806  sub listStudents { Line 838  sub listStudents {
     $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ?       $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
  &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};   &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
   
     my $result='<h3><span class="LC_info">&nbsp;'.$viewgrade.      my $result='<h3><span class="LC_info">&nbsp;'
  ' Submissions for a Student or a Group of Students</span></h3>';   .&mt("$viewgrade Submissions for a Student or a Group of Students")
    .'</span></h3>';
   
     my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));      my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
   
       my %lt = &Apache::lonlocal::texthash (
    'multiple' => 'Please select a student or group of students before clicking on the Next button.',
    'single'   => 'Please select the student before clicking on the Next button.',
        );
     $request->print(<<LISTJAVASCRIPT);      $request->print(<<LISTJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
     function checkSelect(checkBox) {      function checkSelect(checkBox) {
Line 822  sub listStudents { Line 859  sub listStudents {
     ctr++;      ctr++;
  }   }
     }      }
     sense = "a student or group of students";      sense = '$lt{'multiple'}';
  } else {   } else {
     if (checkBox.checked) {      if (checkBox.checked) {
  ctr = 1;   ctr = 1;
     }      }
     sense = "the student";      sense = '$lt{'single'}';
  }   }
  if (ctr == 0) {   if (ctr == 0) {
     alert("Please select "+sense+" before clicking on the Next button.");      alert(sense);
     return false;      return false;
  }   }
  document.gradesub.submit();   document.gradesub.submit();
Line 850  LISTJAVASCRIPT Line 887  LISTJAVASCRIPT
     my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';      my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';
     my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';      my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';
     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.      my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
  "\n".$table.   "\n".$table;
  '&nbsp;<b>View Problem Text: </b><label><input type="radio" name="vProb" value="no" checked="checked" /> no </label>'."\n".  
  '<label><input type="radio" name="vProb" value="yes" /> one student </label>'."\n".      $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
  '<label><input type="radio" name="vProb" value="all" /> all students </label><br />'."\n".      $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
  '&nbsp;<b>View Answer: </b><label><input type="radio" name="vAns" value="no"  /> no </label>'."\n".                    .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
  '<label><input type="radio" name="vAns" value="yes" /> one student </label>'."\n".                    .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
  '<label><input type="radio" name="vAns" value="all" checked="checked" /> all students </label><br />'."\n".                    .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
  '&nbsp;<b>Submissions: </b>'."\n";                    .&Apache::lonhtmlcommon::row_closure();
       $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
                     .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
                     .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
                     .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
                     .&Apache::lonhtmlcommon::row_closure();
   
       my $submission_options;
     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {      if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
  $gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n";   $submission_options.=
       '<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> '.&mt('essay part only').' </label>'."\n";
     }      }
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;      my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
     $env{'form.Status'} = $saveStatus;      $env{'form.Status'} = $saveStatus;
     $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n".      $submission_options.=
  '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n".   '<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> '.&mt('last submission only').' </label>'."\n".
  '<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n".   '<label><input type="radio" name="lastSub" value="last" /> '.&mt('last submission &amp; parts info').' </label>'."\n".
  '<label><input type="radio" name="lastSub" value="all" /> all details</label><br />'."\n".   '<label><input type="radio" name="lastSub" value="datesub" /> '.&mt('by dates and submissions').' </label>'."\n".
         '&nbsp;<b>Grading Increments:</b> <select name="increment">'.   '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').'</label>';
         '<option value="1">Whole Points</option>'.      $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions'))
         '<option value=".5">Half Points</option>'.                    .$submission_options
         '<option value=".25">Quarter Points</option>'.                    .&Apache::lonhtmlcommon::row_closure();
         '<option value=".1">Tenths of a Point</option>'.  
         '</select>'.      $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments'))
                     .'<select name="increment">'
                     .'<option value="1">'.&mt('Whole Points').'</option>'
                     .'<option value=".5">'.&mt('Half Points').'</option>'
                     .'<option value=".25">'.&mt('Quarter Points').'</option>'
                     .'<option value=".1">'.&mt('Tenths of a Point').'</option>'
                     .'</select>'
                     .&Apache::lonhtmlcommon::row_closure();
   
       $gradeTable .= 
         &build_section_inputs().          &build_section_inputs().
  '<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".   '<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
  '<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".   '<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
Line 884  LISTJAVASCRIPT Line 938  LISTJAVASCRIPT
  '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";   '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
   
     if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {      if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
  $gradeTable.='<input type="hidden" name="Status"   value="'.$stu_status.'" />'."\n";   $gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
     } else {      } else {
  $gradeTable.='<b>Student Status:</b> '.          $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status'))
     &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';                        .&Apache::lonhtmlcommon::StatusOptions(
                              $saveStatus,undef,1,'javascript:reLoadList(this.form);')
                         .&Apache::lonhtmlcommon::row_closure();
     }      }
   
     $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '.      $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
  'next to the student\'s name(s). Then click on the Next button.<br />'."\n".                    .'<input type="checkbox" name="checkPlag" checked="checked" />'
  '<input type="hidden" name="command" value="processGroup" />'."\n";                    .&Apache::lonhtmlcommon::row_closure(1)
                     .&Apache::lonhtmlcommon::end_pick_box();
   
       $gradeTable .= '<p>'
                     .&mt('To '.lc($viewgrade)." a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.")."\n"
                     .'<input type="hidden" name="command" value="processGroup" />'
                     .'</p>';
   
 # checkall buttons  # checkall buttons
     $gradeTable.=&check_script('gradesub', 'stuinfo');      $gradeTable.=&check_script('gradesub', 'stuinfo');
     $gradeTable.='<input type="button" '."\n".      $gradeTable.='<input type="button" '."\n".
  'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".   'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
  'value="Next->" /> <br />'."\n";   'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
     $gradeTable.=&check_buttons();      $gradeTable.=&check_buttons();
     $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />Check For Plagiarism</label>';  
     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);      my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
     $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.      $gradeTable.= &Apache::loncommon::start_data_table().
  '<table border="0"><tr bgcolor="#e6ffff">';   &Apache::loncommon::start_data_table_header_row();
     my $loop = 0;      my $loop = 0;
     while ($loop < 2) {      while ($loop < 2) {
  $gradeTable.='<td><b>&nbsp;No.</b>&nbsp;</td><td><b>&nbsp;Select&nbsp;</b></td>'.   $gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
     '<td>'.&nameUserString('header').'&nbsp;Section/Group</td>';      '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
  if ($env{'form.showgrading'} eq 'yes'    if ($env{'form.showgrading'} eq 'yes' 
     && $submitonly ne 'queued'      && $submitonly ne 'queued'
     && $submitonly ne 'all') {      && $submitonly ne 'all') {
     foreach (sort(@$partlist)) {      foreach my $part (sort(@$partlist)) {
  my $display_part=&get_display_part((split(/_/))[0],$symb);   my $display_part=
  $gradeTable.='<td><b>&nbsp;Part: '.$display_part.      &get_display_part((split(/_/,$part))[0],$symb);
     ' Status&nbsp;</b></td>';   $gradeTable.=
       '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
     }      }
  } elsif ($submitonly eq 'queued') {   } elsif ($submitonly eq 'queued') {
     $gradeTable.='<td><b>&nbsp;'.&mt('Queue Status').'&nbsp;</b></td>';      $gradeTable.='<th>'.&mt('Queue Status').'&nbsp;</th>';
  }   }
  $loop++;   $loop++;
 # $gradeTable.='<td></td>' if ($loop%2 ==1);  # $gradeTable.='<td></td>' if ($loop%2 ==1);
     }      }
     $gradeTable.='</tr>'."\n";      $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n";
   
     my $ctr = 0;      my $ctr = 0;
     foreach my $student (sort       foreach my $student (sort 
Line 978  LISTJAVASCRIPT Line 1040  LISTJAVASCRIPT
  my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];   my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
         my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];          my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  if ( $perm{'vgr'} eq 'F' ) {   if ( $perm{'vgr'} eq 'F' ) {
     $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);      if ($ctr%2 ==1) {
    $gradeTable.= &Apache::loncommon::start_data_table_row();
       }
     $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.      $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
                '<td align="center"><label><input type=checkbox name="stuinfo" value="'.                 '<td align="center"><label><input type="checkbox" name="stuinfo" value="'.
                $student.':'.$$fullname{$student}.':::SECTION'.$section.                 $student.':'.$$fullname{$student}.':::SECTION'.$section.
        ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.         ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
        &nameUserString(undef,$$fullname{$student},$uname,$udom).         &nameUserString(undef,$$fullname{$student},$uname,$udom).
        '&nbsp;'.$section.'/'.$group.'</td>'."\n";         '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
   
     if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {      if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
  foreach (sort keys(%status)) {   foreach (sort(keys(%status))) {
     next if (/^resource.*?submitted_by$/);      next if ($_ =~ /^resource.*?submitted_by$/);
     $gradeTable.='<td align="center">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";      $gradeTable.='<td align="center">&nbsp;'.&mt($status{$_}).'&nbsp;</td>'."\n";
  }   }
     }      }
 #    $gradeTable.='<td></td>' if ($ctr%2 ==1);  #    $gradeTable.='<td></td>' if ($ctr%2 ==1);
     $gradeTable.='</tr>'."\n" if ($ctr%2 ==0);      if ($ctr%2 ==0) {
    $gradeTable.=&Apache::loncommon::end_data_table_row()."\n";
       }
  }   }
     }      }
     if ($ctr%2 ==1) {      if ($ctr%2 ==1) {
Line 1007  LISTJAVASCRIPT Line 1073  LISTJAVASCRIPT
     } elsif ($submitonly eq 'queued') {      } elsif ($submitonly eq 'queued') {
  $gradeTable.='<td>&nbsp;</td>';   $gradeTable.='<td>&nbsp;</td>';
     }      }
  $gradeTable.='</tr>';   $gradeTable.=&Apache::loncommon::end_data_table_row();
     }      }
   
     $gradeTable.='</table></td></tr></table>'."\n".      $gradeTable.=&Apache::loncommon::end_data_table()."\n".
  '<input type="button" '.   '<input type="button" '.
  'onClick="javascript:checkSelect(this.form.stuinfo);" '.   'onClick="javascript:checkSelect(this.form.stuinfo);" '.
  'value="Next->" /></form>'."\n";   'value="'.&mt('Next').' &rarr;" /></form>'."\n";
     if ($ctr == 0) {      if ($ctr == 0) {
  my $num_students=(scalar(keys(%$fullname)));   my $num_students=(scalar(keys(%$fullname)));
  if ($num_students eq 0) {   if ($num_students eq 0) {
     $gradeTable='<br />&nbsp;<span class="LC_warning">There are no students currently enrolled.</span>';      $gradeTable='<br />&nbsp;<span class="LC_warning">'.&mt('There are no students currently enrolled.').'</span>';
  } else {   } else {
     my $submissions='submissions';      my $submissions='submissions';
     if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }      if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
     if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }      if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
     if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }      if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
     $gradeTable='<br />&nbsp;<span class="LC_warning">'.      $gradeTable='<br />&nbsp;<span class="LC_warning">'.
  'No '.$submissions.' found for this resource for any students. ('.$num_students.   &mt('No '.$submissions.' found for this resource for any students. ([_1] students checked for '.$submissions.')',
  ' students checked for '.$submissions.')</span><br />';      $num_students).
    '</span><br />';
  }   }
     } elsif ($ctr == 1) {      } elsif ($ctr == 1) {
  $gradeTable =~ s/type=checkbox/type=checkbox checked/;   $gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/;
     }      }
     $gradeTable.=&show_grading_menu_form($symb);      $gradeTable.=&show_grading_menu_form($symb);
     $request->print($gradeTable);      $request->print($gradeTable);
Line 1075  sub check_script { Line 1142  sub check_script {
 }  }
   
 sub check_buttons {  sub check_buttons {
     my $buttons.='<input type="button" onclick="checkall()" value="Check All" />';      my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
     $buttons.='<input type="button" onclick="uncheckall()" value="Uncheck All" />&nbsp;';      $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" />&nbsp;';
     $buttons.='<input type="button" onclick="checksec()" value="Check Section/Group" />';      $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
     $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';      $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
     return $buttons;      return $buttons;
 }  }
Line 1108  sub processGroup { Line 1175  sub processGroup {
 #--- Javascript to handle the submission page functionality ---  #--- Javascript to handle the submission page functionality ---
 sub sub_page_js {  sub sub_page_js {
     my $request = shift;      my $request = shift;
       my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
     $request->print(<<SUBJAVASCRIPT);      $request->print(<<SUBJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
     function updateRadio(formname,id,weight) {      function updateRadio(formname,id,weight) {
Line 1118  sub sub_page_js { Line 1186  sub sub_page_js {
  gradeBox.value = pts;   gradeBox.value = pts;
  var resetbox = false;   var resetbox = false;
  if (isNaN(pts) || pts < 0) {   if (isNaN(pts) || pts < 0) {
     alert("A number equal or greater than 0 is expected. Entered value = "+pts);      alert("$alertmsg"+pts);
     for (var i=0; i<radioButton.length; i++) {      for (var i=0; i<radioButton.length; i++) {
  if (radioButton[i].checked) {   if (radioButton[i].checked) {
     gradeBox.value = i;      gradeBox.value = i;
Line 1363  INNERJS Line 1431  INNERJS
   
     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();      my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
     $docopen=~s/^document\.//;      $docopen=~s/^document\.//;
       my $alertmsg = &mt('Please select a word or group of words from document and then click this link.');
     $request->print(<<SUBJAVASCRIPT);      $request->print(<<SUBJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
   
Line 1395  INNERJS Line 1464  INNERJS
     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("Please select a word or group of words from document and then click this link.");   alert("$alertmsg");
  return;   return;
     }      }
     var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);      var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
Line 1480  INNERJS Line 1549  INNERJS
   
     pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");      pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");      pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
     pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Compose Message for \"+fullname+\"</span></h3><br /><br />");      pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />");
   
     pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");      pDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
     pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");      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>");      pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>");
 }  }
     function displaySubject(msg,shwsel) {      function displaySubject(msg,shwsel) {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pDoc.write("<td>Subject</td>");      pDoc.write("<td>Subject<\\/td>");
     pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></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>");      pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"><\\/td><\\/tr>");
 }  }
   
   function displaySavedMsg(ctr,msg,shwsel) {    function displaySavedMsg(ctr,msg,shwsel) {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pDoc.write("<td align=\\"center\\">"+ctr+"</td>");      pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
     pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>");      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>");      pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");
 }  }
   
   function newMsg(newmsg,shwsel) {    function newMsg(newmsg,shwsel) {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pDoc.write("<td align=\\"center\\">New</td>");      pDoc.write("<td align=\\"center\\">New<\\/td>");
     pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></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>");      pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");
 }  }
   
   function msgTail() {    function msgTail() {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("</table>");      pDoc.write("<\\/table>");
     pDoc.write("</td></tr></table>&nbsp;");      pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
     pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&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("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
     pDoc.write("</form>");      pDoc.write("<\\/form>");
     pDoc.write('$end_page_msg_central');      pDoc.write('$end_page_msg_central');
     pDoc.close();      pDoc.close();
 }  }
Line 1565  INNERJS Line 1634  INNERJS
     hDoc.$docopen;      hDoc.$docopen;
     hDoc.write('$start_page_highlight_central');      hDoc.write('$start_page_highlight_central');
     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");      hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
     hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options</span></h3><br /><br />");      hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options<\\/span><\\/h3><br /><br />");
   
     hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");      hDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
     hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");      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>");      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) {     function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
     var hDoc = hwdWin.document;      var hDoc = hwdWin.document;
     hDoc.write("<tr bgcolor=\\"#ffffdd\\">");      hDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     hDoc.write("<td align=\\"left\\">");      hDoc.write("<td align=\\"left\\">");
     hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"</td>");      hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"<\\/td>");
     hDoc.write("<td align=\\"left\\">");      hDoc.write("<td align=\\"left\\">");
     hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"</td>");      hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"<\\/td>");
     hDoc.write("<td align=\\"left\\">");      hDoc.write("<td align=\\"left\\">");
     hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"</td>");      hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"<\\/td>");
     hDoc.write("</tr>");      hDoc.write("<\\/tr>");
   }    }
   
   function highlightend() {     function highlightend() { 
     var hDoc = hwdWin.document;      var hDoc = hwdWin.document;
     hDoc.write("</table>");      hDoc.write("<\\/table>");
     hDoc.write("</td></tr></table>&nbsp;");      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=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");
     hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");      hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
     hDoc.write("</form>");      hDoc.write("<\\/form>");
     hDoc.write('$end_page_highlight_central');      hDoc.write('$end_page_highlight_central');
     hDoc.close();      hDoc.close();
   }    }
Line 1612  sub get_increment { Line 1681  sub get_increment {
 sub gradeBox {  sub gradeBox {
     my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;      my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
     my $checkIcon = '<img alt="'.&mt('Check Mark').      my $checkIcon = '<img alt="'.&mt('Check Mark').
  '" src="'.$request->dir_config('lonIconsURL').   '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
  '/check.gif" height="16" border="0" />';  
     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);      my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
     my $wgtmsg = ($wgt > 0 ? '(problem weight)' :       my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)') 
   '<span class="LC_info">problem weight assigned by computer</span>');                             : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
     $wgt       = ($wgt > 0 ? $wgt : '1');      $wgt       = ($wgt > 0 ? $wgt : '1');
     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?      my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
   '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));    '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
     my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";      my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
     my $display_part=&get_display_part($partid,$symb);      my $display_part= &get_display_part($partid,$symb);
     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},      my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
        [$partid]);         [$partid]);
     my $aggtries = $$record{'resource.'.$partid.'.tries'};      my $aggtries = $$record{'resource.'.$partid.'.tries'};
     if ($last_resets{$partid}) {      if ($last_resets{$partid}) {
         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);          $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
     }      }
     $result.='<table border="0"><tr><td>'.      $result.='<table border="0"><tr>';
  '<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";  
     my $ctr = 0;      my $ctr = 0;
     my $thisweight = 0;      my $thisweight = 0;
     my $increment = &get_increment();      my $increment = &get_increment();
     $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across  
       my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
     while ($thisweight<=$wgt) {      while ($thisweight<=$wgt) {
  $result.= '<td><span style="white-space: nowrap;"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.   $radio.= '<td><span class="LC_nobreak"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
     'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.      'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
     $thisweight.')" value="'.$thisweight.'" '.      $thisweight.')" value="'.$thisweight.'" '.
     ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";      ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
  $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');   $radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
         $thisweight += $increment;          $thisweight += $increment;
  $ctr++;   $ctr++;
     }      }
     $result.='</tr></table>';      $radio.='</tr></table>';
     $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>'."\n";  
     $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.      my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
  ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.   ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
  'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.   'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
  $wgt.')" /></td>'."\n";   $wgt.')" /></td>'."\n";
     $result.='<td>/'.$wgt.' '.$wgtmsg.      $line.='<td>/'.$wgt.' '.$wgtmsg.
  ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').   ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
  ' </td><td>'."\n";   ' </td><td><b>'.&mt('Grade Status').':</b>'."\n";
     $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.      $line.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
  'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";   'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {      if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
  $result.='<option></option>'.   $line.='<option></option>'.
     '<option selected="selected">excused</option>';      '<option value="excused" selected="selected">'.&mt('excused').'</option>';
     } else {      } else {
  $result.='<option selected="selected"></option>'.   $line.='<option selected="selected"></option>'.
     '<option>excused</option>';      '<option value="excused" >'.&mt('excused').'</option>';
     }      }
     $result.='<option>reset status</option></select>'."\n";      $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
     $result.="&nbsp;&nbsp;\n";  
   
    #&mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line);
       $result .= 
       '<td><b>'.&mt('Part').':</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points').':</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'.
       
       $result.='</tr></table>'."\n";
     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\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="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
  '<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.   '<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
Line 1671  sub gradeBox { Line 1745  sub gradeBox {
         $$record{'resource.'.$partid.'.tries'}.'" />'."\n".          $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.          '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
         $aggtries.'" />'."\n";          $aggtries.'" />'."\n";
     $result.='</td></tr></table>'."\n";  
     $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);      $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);
     return $result;      return $result;
 }  }
Line 1703  sub handback_box { Line 1776  sub handback_box {
     '<span class="LC_filename">'.$file_disp.'</span>');      '<span class="LC_filename">'.$file_disp.'</span>');
            $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";             $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
            $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />';             $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />';
            $result.='(File will be uploaded when you click on Save & Next below.)<br />';             $result.='('.&mt('File will be uploaded when you click on Save &amp; Next below.').')<br />';
            $file_counter++;             $file_counter++;
     }      }
  }   }
Line 1739  sub show_problem { Line 1812  sub show_problem {
  $companswer=~s|</form>||g;   $companswer=~s|</form>||g;
  $companswer=~s|name="submit"|name="would_have_been_submit"|g;   $companswer=~s|name="submit"|name="would_have_been_submit"|g;
     }      }
     my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';      $rendered=
     $result.='<table border="0" width="100%">';   '<div class="LC_grade_show_problem_header">'.
     if ($viewon) {   &mt('View of the problem').
  $result.='<tr><td bgcolor="#e6ffff"><b> ';   '</div><div class="LC_grade_show_problem_problem">'.
  if ($mode eq 'both' or $mode eq 'text') {   $rendered.
     $result.='View of the problem - ';   '</div>';
  } else {      $companswer=
     $result.='Correct answer: ';   '<div class="LC_grade_show_problem_header">'.
  }   &mt('Correct answer').
  $result.=$env{'form.fullname'}.'</b></td></tr>';   '</div><div class="LC_grade_show_problem_problem">'.
     }   $companswer.
    '</div>';
       my $result;
     if ($mode eq 'both') {      if ($mode eq 'both') {
  $result.='<tr><td bgcolor="#ffffff">'.$rendered.'<br />';   $result=$rendered.$companswer;
  $result.='<b>Correct answer:</b><br />'.$companswer;  
     } elsif ($mode eq 'text') {      } elsif ($mode eq 'text') {
  $result.='<tr><td bgcolor="#ffffff">'.$rendered;   $result=$rendered;
     } elsif ($mode eq 'answer') {      } elsif ($mode eq 'answer') {
  $result.='<tr><td bgcolor="#ffffff">'.$companswer;   $result=$companswer;
     }      }
     $result.='</td></tr></table>';      $result='<div class="LC_grade_show_problem">'.$result.'</div>';
     $result.='</td></tr></table><br />';  
     return $result;      return $result;
 }  }
   
Line 1792  sub download_all_link { Line 1865  sub download_all_link {
  join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));   join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
   
     my $identifier = &Apache::loncommon::get_cgi_id();      my $identifier = &Apache::loncommon::get_cgi_id();
     &Apache::lonnet::appenv('cgi.'.$identifier.'.students' => $all_students,      &Apache::lonnet::appenv({'cgi.'.$identifier.'.students' => $all_students,
                             'cgi.'.$identifier.'.symb' => $symb,                               'cgi.'.$identifier.'.symb' => $symb,
                             'cgi.'.$identifier.'.parts' => $parts,);                               'cgi.'.$identifier.'.parts' => $parts,});
     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.      $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
       &mt('Download All Submitted Documents').'</a>');        &mt('Download All Submitted Documents').'</a>');
     return      return
Line 1849  sub submission { Line 1922  sub submission {
  if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {   if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {
     &download_all_link($request, $symb);      &download_all_link($request, $symb);
  }   }
  $request->print('<h3>&nbsp;<span class="LC_info">Submission Record</span></h3>'."\n".   $request->print('<h3>&nbsp;<span class="LC_info">'.&mt('Submission Record').'</span></h3>'."\n".
  '<h4>&nbsp;<b>Resource: </b>'.$env{'form.probTitle'}.'</h4>'."\n");   '<h4>&nbsp;'.&mt('<b>Resource: </b> [_1]',$env{'form.probTitle'}).'</h4>'."\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    # option to display problem, only once else it cause problems 
         # with the form later since the problem has a form.          # with the form later since the problem has a form.
Line 1959  KEYWORDS Line 2026  KEYWORDS
     }      }
   
 # This is where output for one specific student would start  # This is where output for one specific student would start
     my $bgcolor='#DDEEDD';      my $add_class = ($counter%2) ? 'LC_grade_show_user_odd_row' : '';
     if (int($counter/2) eq $counter) { $bgcolor='#DDDDEE'; }  
     $request->print("\n\n".      $request->print("\n\n".
                     '<p><table border="2"><tr><th bgcolor="'.$bgcolor.'">'.$env{'form.fullname'}.'</th></tr><tr><td bgcolor="'.$bgcolor.'">');                      '<div class="LC_grade_show_user '.$add_class.'">'.
       '<div class="LC_grade_user_name">'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</div>'.
       '<div class="LC_grade_show_user_body">'."\n");
   
     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {      if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
  my $mode;   my $mode;
Line 1974  KEYWORDS Line 2042  KEYWORDS
     $mode='answer';      $mode='answer';
  }   }
  &Apache::lonxml::clear_problem_counter();   &Apache::lonxml::clear_problem_counter();
  $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode));   $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,{'request.prefix' => 'ctr'.$counter}));
     }      }
   
     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);      my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
Line 1982  KEYWORDS Line 2050  KEYWORDS
   
     # Display student info      # Display student info
     $request->print(($counter == 0 ? '' : '<br />'));      $request->print(($counter == 0 ? '' : '<br />'));
     my $result='<table border="0" width="100%"><tr><td bgcolor="#777777">'."\n".      my $result='<div class="LC_grade_submissions">';
  '<table border="0" width="100%"><tr bgcolor="#edffff"><td>'."\n";      
       $result.='<div class="LC_grade_submissions_header">';
     $result.='<b>Fullname: </b>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'<br />'."\n";      $result.= &mt('Submissions');
     $result.='<input type="hidden" name="name'.$counter.      $result.='<input type="hidden" name="name'.$counter.
  '" value="'.$env{'form.fullname'}.'" />'."\n";   '" value="'.$env{'form.fullname'}.'" />'."\n";
       if ($env{'form.handgrade'} eq 'no') {
    $result.='<span class="LC_grade_check_note">'.
       &mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)."</span>\n";
   
       }
   
   
   
     # If any part of the problem is an essay-response (handgraded), then check for collaborators      # If any part of the problem is an essay-response (handgraded), then check for collaborators
     my @col_fullnames;      my $fullname;
     my ($classlist,$fullname);      my $col_fullnames = [];
     if ($env{'form.handgrade'} eq 'yes') {      if ($env{'form.handgrade'} eq 'yes') {
  ($classlist,undef,$fullname) = &getclasslist('all','0');   (my $sub_result,$fullname,$col_fullnames)=
  for (keys (%$handgrade)) {      &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
     my $ncol = &Apache::lonnet::EXT('resource.'.$_.   $counter);
     '.maxcollaborators',   $result.=$sub_result;
                                             $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");      $request->print($result."\n");
       $request->print('</div>'."\n");
     # print student answer/submission      # print student answer/submission
     # Options are (1) Handgaded submission only      # Options are (1) Handgaded submission only
     #             (2) Last submission, includes submission that is not handgraded       #             (2) Last submission, includes submission that is not handgraded 
Line 2055  KEYWORDS Line 2083  KEYWORDS
     #             (4) The whole record for this student      #             (4) The whole record for this student
     if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {      if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
  my ($string,$timestamp)= &get_last_submission(\%record);   my ($string,$timestamp)= &get_last_submission(\%record);
  my $lastsubonly=''.  
     ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '.   my $lastsubonly;
      $$timestamp)."</td></tr>\n";  
  if ($$timestamp eq '') {   if ($$timestamp eq '') {
     $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0];       $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
  } else {   } else {
       $lastsubonly = '<div class="LC_grade_submissions_body"> <b>Date Submitted:</b> '.$$timestamp."\n";
   
     my %seenparts;      my %seenparts;
     my @part_response_id = &flatten_responseType($responseType);      my @part_response_id = &flatten_responseType($responseType);
     foreach my $part (@part_response_id) {      foreach my $part (@part_response_id) {
Line 2083  KEYWORDS Line 2113  KEYWORDS
  }   }
  my $responsetype = $responseType->{$partid}->{$respid};   my $responsetype = $responseType->{$partid}->{$respid};
  if (!exists($record{"resource.$partid.$respid.submission"})) {   if (!exists($record{"resource.$partid.$respid.submission"})) {
     $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.      $lastsubonly.="\n".'<div class="LC_grade_submission_part"><b>Part:</b> '.
  $display_part.' <span class="LC_internal_info">( ID '.$respid.   $display_part.' <span class="LC_internal_info">( ID '.$respid.
  ' )</span>&nbsp; &nbsp;'.   ' )</span>&nbsp; &nbsp;'.
  '<span class="LC_warning">Nothing submitted - no attempts</span><br /><br />';   '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
     next;      next;
  }   }
  foreach (@$string) {   foreach my $submission (@$string) {
     my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/;      my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
     if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }      if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
     my ($ressub,$subval) = split(/:/,$_,2);      my ($ressub,$subval) = split(/:/,$submission,2);
     # Similarity check      # Similarity check
     my $similar='';      my $similar='';
     if($env{'form.checkPlag'}){      if($env{'form.checkPlag'}){
Line 2105  KEYWORDS Line 2135  KEYWORDS
    {'one_time' => 1});     {'one_time' => 1});
   
     $similar="<hr /><h3><span class=\"LC_warning\">".      $similar="<hr /><h3><span class=\"LC_warning\">".
  &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])',   &mt('Essay is [_1]% similar to an essay by [_2] in course [_3] (course id [_4]:[_5])',
     $osim,      $osim,
     &Apache::loncommon::plainname($oname,$odom),      &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')',
     $oname,$odom,  
     $old_course_desc{'description'},      $old_course_desc{'description'},
     $old_course_desc{'num'},      $old_course_desc{'num'},
     $old_course_desc{'domain'}).      $old_course_desc{'domain'}).
Line 2122  KEYWORDS Line 2151  KEYWORDS
  ($env{'form.lastSub'} eq 'hdgrade' &&    ($env{'form.lastSub'} eq 'hdgrade' && 
  $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {   $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
  my $display_part=&get_display_part($partid,$symb);   my $display_part=&get_display_part($partid,$symb);
  $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.   $lastsubonly.='<div class="LC_grade_submission_part"><b>Part:</b> '.
     $display_part.' <span class="LC_internal_info">( ID '.$respid.      $display_part.' <span class="LC_internal_info">( ID '.$respid.
     ' )</span>&nbsp; &nbsp;';      ' )</span>&nbsp; &nbsp;';
  my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);   my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
  if (@$files) {   if (@$files) {
     $lastsubonly.='<br /><span class="LC_warning">Like all files provided by users, this file may contain virusses</span><br />';      $lastsubonly.='<br /><span class="LC_warning">'.&mt('Like all files provided by users, this file may contain viruses').'</span><br />';
     my $file_counter = 0;      my $file_counter = 0;
     foreach my $file (@$files) {      foreach my $file (@$files) {
         $file_counter ++;          $file_counter++;
  &Apache::lonnet::allowuploaded('/adm/grades',$file);   &Apache::lonnet::allowuploaded('/adm/grades',$file);
  $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';   $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" /> '.$file.'</a>';
     }      }
     $lastsubonly.='<br />';      $lastsubonly.='<br />';
  }   }
  $lastsubonly.='<b>Submitted Answer: </b>'.   $lastsubonly.='<b>'.&mt('Submitted Answer:').' </b>'.
     &cleanRecord($subval,$responsetype,$symb,$partid,      &cleanRecord($subval,$responsetype,$symb,$partid,
  $respid,\%record,$order);   $respid,\%record,$order,undef,$uname,$udom);
  if ($similar) {$lastsubonly.="<br /><br />$similar\n";}   if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
    $lastsubonly.='</div>';
     }      }
  }   }
     }      }
       $lastsubonly.='</div>'."\n";
  }   }
  $lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\n";  
  $request->print($lastsubonly);   $request->print($lastsubonly);
     } elsif ($env{'form.lastSub'} eq 'datesub') {     } elsif ($env{'form.lastSub'} eq 'datesub') {
  my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);   my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);
  $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));   $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
     } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {      } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
Line 2158  KEYWORDS Line 2188  KEYWORDS
   
     $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'      $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
  .$udom.'" />'."\n");   .$udom.'" />'."\n");
       
     # return if view submission with no grading option      # return if view submission with no grading option
     if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {      if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
  my $toGrade.='<input type="button" value="Grade Student" '.   my $toGrade.='<input type="button" value="Grade Student" '.
     'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''      'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
     .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));      .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
  $toGrade.='</td></tr></table></td></tr></table>'."\n";   $toGrade.='</div>'."\n";
  if (($env{'form.command'} eq 'submission') ||    if (($env{'form.command'} eq 'submission') || 
     ($env{'form.command'} eq 'processGroup' && $counter == $total)) {      ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
     $toGrade.='</form>'.&show_grading_menu_form($symb);       $toGrade.='</form>'.&show_grading_menu_form($symb); 
Line 2172  KEYWORDS Line 2201  KEYWORDS
  $request->print($toGrade);   $request->print($toGrade);
  return;   return;
     } else {      } else {
  $request->print('</td></tr></table></td></tr></table>'."\n");   $request->print('</div>'."\n");
     }      }
   
     # essay grading message center      # essay grading message center
     if ($env{'form.handgrade'} eq 'yes') {      if ($env{'form.handgrade'} eq 'yes') {
    my $result='<div class="LC_grade_message_center">';
       
    $result.='<div class="LC_grade_message_center_header">'.
       &mt('Send Message').'</div><div class="LC_grade_message_center_body">';
  my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});   my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
  my $msgfor = $givenn.' '.$lastname;   my $msgfor = $givenn.' '.$lastname;
  if (scalar(@col_fullnames) > 0) {   if (scalar(@$col_fullnames) > 0) {
     my $lastone = pop @col_fullnames;      my $lastone = pop(@$col_fullnames);
     $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.';      $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
  }   }
  $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript   $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
  $result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".   $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
     '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";      '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
  $result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.   $result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
     ',\''.$msgfor.'\');" target="_self">'.      ',\''.$msgfor.'\');" target="_self">'.
     &mt('Compose message to student').(scalar(@col_fullnames) >= 1 ? 's' : '').'</a><label> ('.      &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').'</a><label> ('.
     &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.      &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
     '<img src="'.$request->dir_config('lonIconsURL').      '<img src="'.$request->dir_config('lonIconsURL').
     '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".      '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
     '<br />&nbsp;('.      '<br />&nbsp;('.
     &mt('Message will be sent when you click on Save & Next below.').")\n";      &mt('Message will be sent when you click on Save &amp; Next below.').")\n";
    $result.='</div></div>';
  $request->print($result);   $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 %seen = ();
     my @partlist;      my @partlist;
     my @gradePartRespid;      my @gradePartRespid;
     my @part_response_id = &flatten_responseType($responseType);      my @part_response_id = &flatten_responseType($responseType);
       $request->print('<div class="LC_grade_assign">'.
       
       '<div class="LC_grade_assign_header">'.
       &mt('Assign Grades').'</div>'.
       '<div class="LC_grade_assign_body">');
     foreach my $part_response_id (@part_response_id) {      foreach my $part_response_id (@part_response_id) {
     my ($partid,$respid) = @{ $part_response_id };      my ($partid,$respid) = @{ $part_response_id };
  my $part_resp = join('_',@{ $part_response_id });   my $part_resp = join('_',@{ $part_response_id });
Line 2218  KEYWORDS Line 2247  KEYWORDS
  $seen{$partid}++;   $seen{$partid}++;
  next if ($$handgrade{$part_resp} ne 'yes'    next if ($$handgrade{$part_resp} ne 'yes' 
  && $env{'form.lastSub'} eq 'hdgrade');   && $env{'form.lastSub'} eq 'hdgrade');
  push @partlist,$partid;   push(@partlist,$partid);
  push @gradePartRespid,$partid.'.'.$respid;   push(@gradePartRespid,$partid.'.'.$respid);
  $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));   $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
     }      }
       $request->print('</div></div>');
   
       $request->print('<div class="LC_grade_info_links">');
       if ($perm{'vgr'}) {
    $request->print(
       &Apache::loncommon::track_student_link(&mt('View recent activity'),
      $uname,$udom,'check'));
       }
       if ($perm{'opa'}) {
    $request->print(
       &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),
    $uname,$udom,$symb,'check'));
       }
       $request->print('</div>');
   
     $result='<input type="hidden" name="partlist'.$counter.      $result='<input type="hidden" name="partlist'.$counter.
  '" value="'.(join ":",@partlist).'" />'."\n";   '" value="'.(join ":",@partlist).'" />'."\n";
     $result.='<input type="hidden" name="gradePartRespid'.      $result.='<input type="hidden" name="gradePartRespid'.
Line 2232  KEYWORDS Line 2276  KEYWORDS
     $partlist[$ctr].'" />'."\n";      $partlist[$ctr].'" />'."\n";
  $ctr++;   $ctr++;
     }      }
     $request->print($result.'</td></tr></table></td></tr></table>'."\n");      $request->print($result.''."\n");
   
 # Done with printing info for one student  # Done with printing info for one student
   
     $request->print('</td></tr></table></p>');      $request->print('</div>');#LC_grade_show_user_body
       $request->print('</div>');#LC_grade_show_user
   
   
     # print end of form      # print end of form
     if ($counter == $total) {      if ($counter == $total) {
  my $endform='<table border="0"><tr><td>'."\n";   my $endform='<table border="0"><tr><td>'."\n";
  $endform.='<input type="button" value="Save & Next" '.   $endform.='<input type="button" value="'.&mt('Save &amp; Next').'" '.
     'onClick="javascript:checksubmit(this.form,\'Save & Next\','.      'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
     $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";      $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
  my $ntstu ='<select name="NTSTU">'.   my $ntstu ='<select name="NTSTU">'.
Line 2251  KEYWORDS Line 2296  KEYWORDS
     '<option>7</option><option>10</option></select>'."\n";      '<option>7</option><option>10</option></select>'."\n";
  my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');   my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
  $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;   $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
  $endform.=$ntstu.'student(s) &nbsp;&nbsp;';   $endform.=&mt('[quant,_1,student]',$ntstu);
  $endform.='<input type="button" value="Previous" '.   $endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('Previous').'" '.
     'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".      'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
     '<input type="button" value="Next" '.      '<input type="button" value="'.&mt('Next').'" '.
     'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';      'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
  $endform.='(Next and Previous (student) do not save the scores.)'."\n" ;   $endform.=&mt('(Next and Previous (student) do not save the scores.)')."\n" ;
         $endform.="<input type='hidden' value='".&get_increment().          $endform.="<input type='hidden' value='".&get_increment().
             "' name='increment' />";              "' name='increment' />";
  $endform.='</td><tr></table></form>';   $endform.='</td></tr></table></form>';
  $endform.=&show_grading_menu_form($symb);   $endform.=&show_grading_menu_form($symb);
  $request->print($endform);   $request->print($endform);
     }      }
     return '';      return '';
 }  }
   
   sub check_collaborators {
       my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
       my ($result,@col_fullnames);
       my ($classlist,undef,$fullname) = &getclasslist('all','0');
       foreach my $part (keys(%$handgrade)) {
    my $ncol = &Apache::lonnet::EXT('resource.'.$part.
    '.maxcollaborators',
    $symb,$udom,$uname);
    next if ($ncol <= 0);
    $part =~ s/\_/\./g;
    next if ($record->{'resource.'.$part.'.collaborators'} eq '');
    my (@good_collaborators, @bad_collaborators);
    foreach my $possible_collaborator
       (split(/,?\s+/,$record->{'resource.'.$part.'.collaborators'})) { 
       $possible_collaborator =~ s/[\$\^\(\)]//g;
       next if ($possible_collaborator eq '');
       my ($co_name,$co_dom) = split(/\@|:/,$possible_collaborator);
       $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
       next if ($co_name eq $uname && $co_dom eq $udom);
       # Doing this grep allows 'fuzzy' specification
       my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i, 
          keys(%$classlist));
       if (! scalar(@matches)) {
    push(@bad_collaborators, $possible_collaborator);
       } else {
    push(@good_collaborators, @matches);
       }
    }
    if (scalar(@good_collaborators) != 0) {
       $result.='<br />'.&mt('Collaborators: ');
       foreach my $name (@good_collaborators) {
    my ($lastname,$givenn) = split(/,/,$$fullname{$name});
    push(@col_fullnames, $givenn.' '.$lastname);
    $result.=$fullname->{$name}.'&nbsp; &nbsp; &nbsp;';
       }
       $result.='<br />'."\n";
       my ($part)=split(/\./,$part);
       $result.='<input type="hidden" name="collaborator'.$counter.
    '" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
    "\n";
    }
    if (scalar(@bad_collaborators) > 0) {
       $result.='<div class="LC_warning">';
       $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
       $result .= '</div>';
    }         
    if (scalar(@bad_collaborators > $ncol)) {
       $result .= '<div class="LC_warning">';
       $result .= &mt('This student has submitted too many '.
    'collaborators.  Maximum is [_1].',$ncol);
       $result .= '</div>';
    }
       }
       return ($result,$fullname,\@col_fullnames);
   }
   
 #--- Retrieve the last submission for all the parts  #--- Retrieve the last submission for all the parts
 sub get_last_submission {  sub get_last_submission {
     my ($returnhash)=@_;      my ($returnhash)=@_;
Line 2278  sub get_last_submission { Line 2379  sub get_last_submission {
  $$returnhash{$version.':keys'}))) {   $$returnhash{$version.':keys'}))) {
  $lasthash{$key}=$$returnhash{$version.':'.$key};   $lasthash{$key}=$$returnhash{$version.':'.$key};
  $timestamp =    $timestamp = 
     scalar(localtime($$returnhash{$version.':timestamp'}));      &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
     }      }
  }   }
  foreach my $key (keys(%lasthash)) {   foreach my $key (keys(%lasthash)) {
Line 2292  sub get_last_submission { Line 2393  sub get_last_submission {
     }      }
     if (!@string) {      if (!@string) {
  $string[0] =   $string[0] =
     '<span class="LC_warning">Nothing submitted - no attempts.</span>';      '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span>';
     }      }
     return (\@string,\$timestamp);      return (\@string,\$timestamp);
 }  }
Line 2361  sub processHandGrade { Line 2462  sub processHandGrade {
                                                      undef,$feedurl,undef,                                                       undef,$feedurl,undef,
                                                      undef,undef,$showsymb,                                                       undef,undef,$showsymb,
                                                      $restitle);                                                       $restitle);
  $request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.   $request->print('<br />'.&mt('Sending message to [_1]',$uname.':'.$udom).': '.
  $msgstatus);   $msgstatus);
     }      }
     if ($env{'form.collaborator'.$ctr}) {      if ($env{'form.collaborator'.$ctr}) {
Line 2474  sub processHandGrade { Line 2575  sub processHandGrade {
   
     my (@parsedlist,@nextlist);      my (@parsedlist,@nextlist);
     my ($nextflg) = 0;      my ($nextflg) = 0;
     foreach (sort       foreach my $item (sort 
      {       {
  if (lc($$fullname{$a}) ne lc($$fullname{$b})) {   if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
      return (lc($$fullname{$a}) cmp lc($$fullname{$b}));       return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
Line 2482  sub processHandGrade { Line 2583  sub processHandGrade {
  return $a cmp $b;   return $a cmp $b;
      } (keys(%$fullname))) {       } (keys(%$fullname))) {
  if ($nextflg == 1 && $button =~ /Next$/) {   if ($nextflg == 1 && $button =~ /Next$/) {
     push @parsedlist,$_;      push(@parsedlist,$item);
  }   }
  $nextflg = 1 if ($_ eq $laststu);   $nextflg = 1 if ($item eq $laststu);
  if ($button eq 'Previous') {   if ($button eq 'Previous') {
     last if ($_ eq $firststu);      last if ($item eq $firststu);
     push @parsedlist,$_;      push(@parsedlist,$item);
  }   }
     }      }
     $ctr = 0;      $ctr = 0;
Line 2510  sub processHandGrade { Line 2611  sub processHandGrade {
     my $submitted = 0;      my $submitted = 0;
     my $ungraded = 0;      my $ungraded = 0;
     my $incorrect = 0;      my $incorrect = 0;
     foreach (keys(%status)) {      foreach my $item (keys(%status)) {
  $submitted = 1 if ($status{$_} ne 'nothing');   $submitted = 1 if ($status{$item} ne 'nothing');
  $ungraded = 1 if ($status{$_} =~ /^ungraded/);   $ungraded = 1 if ($status{$item} =~ /^ungraded/);
  $incorrect = 1 if ($status{$_} =~ /^incorrect/);   $incorrect = 1 if ($status{$item} =~ /^incorrect/);
  my ($foo,$partid,$foo1) = split(/\./,$_);   my ($foo,$partid,$foo1) = split(/\./,$item);
  if ($status{'resource.'.$partid.'.submitted_by'} ne '') {   if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
     $submitted = 0;      $submitted = 0;
  }   }
Line 2525  sub processHandGrade { Line 2626  sub processHandGrade {
     next if (!$ungraded && ($submitonly eq 'graded'));      next if (!$ungraded && ($submitonly eq 'graded'));
     next if (!$incorrect && $submitonly eq 'incorrect');      next if (!$incorrect && $submitonly eq 'incorrect');
  }   }
  push @nextlist,$student if ($ctr < $ntstu);   push(@nextlist,$student) if ($ctr < $ntstu);
  last if ($ctr == $ntstu);   last if ($ctr == $ntstu);
  $ctr++;   $ctr++;
     }      }
Line 2533  sub processHandGrade { Line 2634  sub processHandGrade {
     $ctr = 0;      $ctr = 0;
     my $total = scalar(@nextlist)-1;      my $total = scalar(@nextlist)-1;
   
     foreach (sort @nextlist) {      foreach (sort(@nextlist)) {
  my ($uname,$udom,$submitter) = split(/:/);   my ($uname,$udom,$submitter) = split(/:/);
  $env{'form.student'}  = $uname;   $env{'form.student'}  = $uname;
  $env{'form.userdom'}  = $udom;   $env{'form.userdom'}  = $udom;
Line 2542  sub processHandGrade { Line 2643  sub processHandGrade {
  $ctr++;   $ctr++;
     }      }
     if ($total < 0) {      if ($total < 0) {
  my $the_end = '<h3><span class="LC_info">LON-CAPA User Message</span></h3><br />'."\n";   my $the_end = '<h3><span class="LC_info">'.&mt('LON-CAPA User Message').'</span></h3><br />'."\n";
  $the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n";   $the_end.=&mt('<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.=&mt('Click on the button below to return to the grading menu.').'<br /><br />'."\n";
  $the_end.=&show_grading_menu_form($symb);   $the_end.=&show_grading_menu_form($symb);
  $request->print($the_end);   $request->print($the_end);
     }      }
Line 2579  sub saveHandGrade { Line 2680  sub saveHandGrade {
     }      }
  } elsif ($dropMenu eq 'reset status'   } elsif ($dropMenu eq 'reset status'
  && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts   && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
     foreach my $key (keys (%record)) {      foreach my $key (keys(%record)) {
  if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }   if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
     }      }
     $newrecord{'resource.'.$new_part.'.regrader'}=      $newrecord{'resource.'.$new_part.'.regrader'}=
Line 2614  sub saveHandGrade { Line 2715  sub saveHandGrade {
                 &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);                  &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
  next;   next;
     } else {      } else {
         push @parts_graded, $new_part;          push(@parts_graded,$new_part);
     }      }
     if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {      if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
  $newrecord{'resource.'.$new_part.'.awarded'}  = $partial;   $newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
Line 2641  sub saveHandGrade { Line 2742  sub saveHandGrade {
         $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||          $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
         $dropMenu eq 'reset status')          $dropMenu eq 'reset status')
    {     {
     push (@version_parts,$new_part);      push(@version_parts,$new_part);
  }   }
     }      }
     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
Line 2689  sub check_and_remove_from_queue { Line 2790  sub check_and_remove_from_queue {
   
 sub handback_files {  sub handback_files {
     my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;      my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
     my $portfolio_root = &propath($domain,$stuname).'/userfiles/portfolio';      my $portfolio_root = '/userfiles/portfolio';
     my ($partlist,$handgrade,$responseType) = &response_type($symb);      my ($partlist,$handgrade,$responseType) = &response_type($symb);
   
     my @part_response_id = &flatten_responseType($responseType);      my @part_response_id = &flatten_responseType($responseType);
Line 2707  sub handback_files { Line 2808  sub handback_files {
                     my ($answer_name,$answer_ver,$answer_ext) =                      my ($answer_name,$answer_ver,$answer_ext) =
         &file_name_version_ext($answer_file);          &file_name_version_ext($answer_file);
     my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);      my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
     my @dir_list = &Apache::lonnet::dirlist($portfolio_path,$domain,$stuname,$portfolio_root);                      my $getpropath = 1;
       my @dir_list = &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,$domain,$stuname,$getpropath);
     my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);      my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                     # fix file name                      # fix file name
                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);                      my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
Line 2715  sub handback_files { Line 2817  sub handback_files {
                                            $newflg.'_'.$part_resp.'_returndoc'.$file_counter,                                             $newflg.'_'.$part_resp.'_returndoc'.$file_counter,
                                            $save_file_name);                                             $save_file_name);
                     if ($result !~ m|^/uploaded/|) {                      if ($result !~ m|^/uploaded/|) {
                         $request->print('<span class="LC_error">An error occurred ('.$result.                          $request->print('<br /><span class="LC_error">'.
                         ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</span><br />');                              &mt('An error occurred ([_1]) while trying to upload [_2].',
                                   $result,$newflg.'_'.$part_resp.'_returndoc'.$file_counter).
                                           '</span>');
                     } else {                      } else {
                         # mark the file as read only                          # mark the file as read only
                         my @files = ($save_file_name);                          my @files = ($save_file_name);
Line 2813  sub decrement_aggs { Line 2917  sub decrement_aggs {
     if ($aggtries == $totaltries) {      if ($aggtries == $totaltries) {
         $decrement{'users'} = 1;          $decrement{'users'} = 1;
     }      }
     foreach my $type (keys (%decrement)) {      foreach my $type (keys(%decrement)) {
         $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};          $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
     }      }
     return;      return;
Line 2843  sub version_portfiles { Line 2947  sub version_portfiles {
     my $version_parts = join('|',@$v_flag);      my $version_parts = join('|',@$v_flag);
     my @returned_keys;      my @returned_keys;
     my $parts = join('|', @$parts_graded);      my $parts = join('|', @$parts_graded);
     my $portfolio_root = &propath($domain,$stu_name).      my $portfolio_root = '/userfiles/portfolio';
  '/userfiles/portfolio';  
     foreach my $key (keys(%$record)) {      foreach my $key (keys(%$record)) {
         my $new_portfiles;          my $new_portfiles;
         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {          if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
Line 2855  sub version_portfiles { Line 2958  sub version_portfiles {
                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);                  my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
  my ($answer_name,$answer_ver,$answer_ext) =   my ($answer_name,$answer_ver,$answer_ext) =
     &file_name_version_ext($answer_file);      &file_name_version_ext($answer_file);
                 my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);                  my $getpropath = 1;    
                   my @dir_list = &Apache::lonnet::dirlist($portfolio_root.$directory,$domain,$stu_name,$getpropath);
                 my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);                  my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                 my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);                  my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
                 if ($new_answer ne 'problem getting file') {                  if ($new_answer ne 'problem getting file') {
Line 2935  sub file_name_version_ext { Line 3039  sub file_name_version_ext {
 sub viewgrades_js {  sub viewgrades_js {
     my ($request) = shift;      my ($request) = shift;
   
       my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
     $request->print(<<VIEWJAVASCRIPT);      $request->print(<<VIEWJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
    function writePoint(partid,weight,point) {     function writePoint(partid,weight,point) {
Line 2943  sub viewgrades_js { Line 3048  sub viewgrades_js {
  if (point == "textval") {   if (point == "textval") {
     point = document.classgrade["TEXTVAL_"+partid].value;      point = document.classgrade["TEXTVAL_"+partid].value;
     if (isNaN(point) || parseFloat(point) < 0) {      if (isNaN(point) || parseFloat(point) < 0) {
  alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));   alert("$alertmsg"+parseFloat(point));
  var resetbox = false;   var resetbox = false;
  for (var i=0; i<radioButton.length; i++) {   for (var i=0; i<radioButton.length; i++) {
     if (radioButton[i].checked) {      if (radioButton[i].checked) {
Line 3041  sub viewgrades_js { Line 3146  sub viewgrades_js {
  var weight = document.classgrade["weight_"+partid].value;   var weight = document.classgrade["weight_"+partid].value;
   
  if (isNaN(point) || parseFloat(point) < 0) {   if (isNaN(point) || parseFloat(point) < 0) {
     alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));      alert("$alertmsg"+parseFloat(point));
     textbox.value = "";      textbox.value = "";
     return;      return;
  }   }
Line 3115  sub viewgrades { Line 3220  sub viewgrades {
     &Apache::lonnet::clear_EXT_cache_status();      &Apache::lonnet::clear_EXT_cache_status();
   
     my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';      my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
     $result.='<h4><b>Current Resource: </b>'.$env{'form.probTitle'}.'</h4>'."\n";      $result.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
   
     #view individual student submission form - called using Javascript viewOneStudent      #view individual student submission form - called using Javascript viewOneStudent
     $result.=&jscriptNform($symb);      $result.=&jscriptNform($symb);
Line 3130  sub viewgrades { Line 3235  sub viewgrades {
  '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".   '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
  '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";   '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
   
     my $sectionClass;      my ($common_header,$specific_header);
     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));  
     if ($env{'form.section'} eq 'all') {      if ($env{'form.section'} eq 'all') {
  $sectionClass='Class </h3>';   $common_header = &mt('Assign Common Grade to Class');
           $specific_header = &mt('Assign Grade to Specific Students in Class');
     } elsif ($env{'form.section'} eq 'none') {      } elsif ($env{'form.section'} eq 'none') {
  $sectionClass=&mt('Students in no Section').'</h3>';          $common_header = &mt('Assign Common Grade to Students in no Section');
    $specific_header = &mt('Assign Grade to Specific Students in no Section');
     } else {      } else {
  $sectionClass=&mt('Students in Section(s) [_1]',$section_display).'</h3>';          my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
           $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
    $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
     }      }
     $result.='<h3>'.&mt('Assign Common Grade To [_1]',$sectionClass);      $result.= '<h3>'.$common_header.'</h3>'.&Apache::loncommon::start_data_table();
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".  
  '<table border=0><tr bgcolor="#ffffdd"><td>';  
     #radio buttons/text box for assigning points for a section or class.      #radio buttons/text box for assigning points for a section or class.
     #handles different parts of a problem      #handles different parts of a problem
     my ($partlist,$handgrade,$responseType) = &response_type($symb);      my ($partlist,$handgrade,$responseType) = &response_type($symb);
     my %weight = ();      my %weight = ();
     my $ctsparts = 0;      my $ctsparts = 0;
     $result.='<table border="0">';  
     my %seen = ();      my %seen = ();
     my @part_response_id = &flatten_responseType($responseType);      my @part_response_id = &flatten_responseType($responseType);
     foreach my $part_response_id (@part_response_id) {      foreach my $part_response_id (@part_response_id) {
Line 3159  sub viewgrades { Line 3264  sub viewgrades {
  my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);   my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
  $weight{$partid} = $wgt eq '' ? '1' : $wgt;   $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);   my $display_part=&get_display_part($partid,$symb);
  $result.='<tr><td><b>Part:</b> '.$display_part.'&nbsp; &nbsp;<b>Point:</b> </td><td>';   my $radio.='<table border="0"><tr>';  
  $result.='<table border="0"><tr>';    
  my $ctr = 0;   my $ctr = 0;
  while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across   while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
     $result.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.      $radio.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.
  'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.   'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
  ','.$ctr.')" />'.$ctr."</label></td>\n";   ','.$ctr.')" />'.$ctr."</label></td>\n";
     $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');      $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
     $ctr++;      $ctr++;
  }   }
  $result.='</tr></table>';   $radio.='</tr></table>';
  $result.= '</td><td><b> or </b><input type="text" name="TEXTVAL_'.   my $line = '<input type="text" name="TEXTVAL_'.
     $partid.'" size="4" '.'onChange="javascript:writePoint(\''.      $partid.'" size="4" '.'onChange="javascript:writePoint(\''.
  $partid.'\','.$weight{$partid}.',\'textval\')" /> /'.   $partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
     $weight{$partid}.' (problem weight)</td>'."\n";      $weight{$partid}.' '.&mt('(problem weight)').'</td>'."\n";
  $result.= '</td><td><select name="SELVAL_'.$partid.'"'.   $line.= '<td><b>'.&mt('Grade Status').':</b><select name="SELVAL_'.$partid.'"'.
     'onChange="javascript:writeRadText(\''.$partid.'\','.      'onChange="javascript:writeRadText(\''.$partid.'\','.
  $weight{$partid}.')"> '.   $weight{$partid}.')"> '.
     '<option selected="selected"> </option>'.      '<option selected="selected"> </option>'.
     '<option>excused</option>'.      '<option value="excused">'.&mt('excused').'</option>'.
     '<option>reset status</option></select></td>'.      '<option value="reset status">'.&mt('reset status').'</option>'.
             '<td><label><input type="checkbox" name="FORCE_'.$partid.'" /> Override "Correct"</label></td></tr>'."\n";      '</select></td>'.
               '<td><label><input type="checkbox" name="FORCE_'.$partid.'" />'.&mt('Override "Correct"').'</label>';
    $line.='<input type="hidden" name="partid_'.
       $ctsparts.'" value="'.$partid.'" />'."\n";
    $line.='<input type="hidden" name="weight_'.
       $partid.'" value="'.$weight{$partid}.'" />'."\n";
   
    $result.=
       &Apache::loncommon::start_data_table_row()."\n".
       '<td><b>'.&mt('Part').':</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points').':</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'.
       &Apache::loncommon::end_data_table_row()."\n";
  $ctsparts++;   $ctsparts++;
     }      }
     $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n".      $result.=&Apache::loncommon::end_data_table()."\n".
  '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';   '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
     $result.='<input type="button" value="Revert to Default" '.      $result.='<input type="button" value="'.&mt('Revert to Default').'" '.
  'onClick="javascript:resetEntry('.$ctsparts.');" target="_self" />';   'onClick="javascript:resetEntry('.$ctsparts.');" />';
   
     #table listing all the students in a section/class      #table listing all the students in a section/class
     #header of table      #header of table
     $result.= '<h3>Assign Grade to Specific Students in '.$sectionClass;      $result.= '<h3>'.$specific_header.'</h3>'.
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".                &Apache::loncommon::start_data_table().
  '<table border=0><tr bgcolor="#deffff"><td>&nbsp;<b>No.</b>&nbsp;</td>'.        &Apache::loncommon::start_data_table_header_row().
  '<td>'.&nameUserString('header')."</td>\n";        '<th>'.&mt('No.').'</th>'.
         '<th>'.&nameUserString('header')."</th>\n";
     my (@parts) = sort(&getpartlist($symb));      my (@parts) = sort(&getpartlist($symb));
     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);      my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
     my @partids = ();      my @partids = ();
     foreach my $part (@parts) {      foreach my $part (@parts) {
  my $display=&Apache::lonnet::metadata($url,$part.'.display');   my $display=&Apache::lonnet::metadata($url,$part.'.display');
  $display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower          my $narrowtext = &mt('Tries');
    $display =~ s|^Number of Attempts|$narrowtext <br />|; # makes the column narrower
  if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }   if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
  my ($partid) = &split_part_type($part);   my ($partid) = &split_part_type($part);
         push(@partids, $partid);          push(@partids,$partid);
  my $display_part=&get_display_part($partid,$symb);   my $display_part=&get_display_part($partid,$symb);
  if ($display =~ /^Partial Credit Factor/) {   if ($display =~ /^Partial Credit Factor/) {
     $result.='<td><b>Score Part:</b> '.$display_part.      $result.='<th>'.
  ' <br /><b>(weight = '.$weight{$partid}.')</b></td>'."\n";   &mt('Score Part: [_1]<br /> (weight = [_2])',
       $display_part,$weight{$partid}).'</th>'."\n";
     next;      next;
       
  } else {   } else {
     $display =~s/\[Part: \Q$partid\E\]/Part:<\/b> $display_part/;      if ($display =~ /Problem Status/) {
    my $grade_status_mt = &mt('Grade Status');
    $display =~ s{Problem Status}{$grade_status_mt<br />};
       }
       my $part_mt = &mt('Part:');
       $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part};
  }   }
  $display =~ s|Problem Status|Grade Status<br />|;  
  $result.='<td><b>'.$display.'</td>'."\n";   $result.='<th>'.$display.'</th>'."\n";
     }      }
     $result.='</tr>';      $result.=&Apache::loncommon::end_data_table_header_row();
   
     my %last_resets =       my %last_resets = 
  &get_last_resets($symb,$env{'request.course.id'},\@partids);   &get_last_resets($symb,$env{'request.course.id'},\@partids);
Line 3239  sub viewgrades { Line 3358  sub viewgrades {
  $result.=&viewstudentgrade($symb,$env{'request.course.id'},   $result.=&viewstudentgrade($symb,$env{'request.course.id'},
    $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);     $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);
     }      }
     $result.='</table></td></tr></table>';      $result.=&Apache::loncommon::end_data_table();
     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";      $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
     $result.='<input type="button" value="Save" '.      $result.='<input type="button" value="'.&mt('Save').'" '.
  'onClick="javascript:submit();" target="_self" /></form>'."\n";   'onClick="javascript:submit();" target="_self" /></form>'."\n";
     if (scalar(%$fullname) eq 0) {      if (scalar(%$fullname) eq 0) {
  my $colspan=3+scalar(@parts);   my $colspan=3+scalar(@parts);
  my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));   my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
         my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));          my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
  $result='<span class="LC_warning">'.   $result='<span class="LC_warning">'.
     &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade',      &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
         $section_display, $stu_status).          $section_display, $stu_status).
     '</span>';      '</span>';
     }      }
Line 3262  sub viewstudentgrade { Line 3381  sub viewstudentgrade {
     my ($uname,$udom) = split(/:/,$student);      my ($uname,$udom) = split(/:/,$student);
     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);      my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
     my %aggregates = ();       my %aggregates = (); 
     my $result='<tr bgcolor="#ffffdd"><td align="right">'.      my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'.
  '<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.   '<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.
  "\n".$ctr.'&nbsp;</td><td>&nbsp;'.   "\n".$ctr.'&nbsp;</td><td>&nbsp;'.
  '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.   '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
Line 3304  sub viewstudentgrade { Line 3423  sub viewstudentgrade {
     $result.='&nbsp;<select name="'.      $result.='&nbsp;<select name="'.
  'GD_'.$student.'_'.$part.'_solved" '.   'GD_'.$student.'_'.$part.'_solved" '.
  'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";   'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
     $result.= (($status eq 'excused') ? '<option> </option><option selected="selected">excused</option>'       $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
  : '<option selected="selected"> </option><option>excused</option>')."\n";   : '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
     $result.='<option>reset status</option>';      $result.='<option value="reset status">'.&mt('reset status').'</option>';
     $result.="</select>&nbsp;</td>\n";      $result.="</select>&nbsp;</td>\n";
  } else {   } else {
     $result.='<input type="hidden" name="'.      $result.='<input type="hidden" name="'.
Line 3317  sub viewstudentgrade { Line 3436  sub viewstudentgrade {
  'value="'.$score.'" size="4" /></td>'."\n";   'value="'.$score.'" size="4" /></td>'."\n";
  }   }
     }      }
     $result.='</tr>';      $result.=&Apache::loncommon::end_data_table_row();
     return $result;      return $result;
 }  }
   
Line 3328  sub editgrades { Line 3447  sub editgrades {
   
     my $symb=&get_symb($request);      my $symb=&get_symb($request);
     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));      my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     my $title='<h3><span class="LC_info">'.&mt('Current Grade Status').'</span></h3>';      my $title='<h2>'.&mt('Current Grade Status').'</h2>';
     $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4><br />'."\n";      $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
     $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";      $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
   
     my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";      my $result= &Apache::loncommon::start_data_table().
     $result.= '<table border="0"><tr bgcolor="#deffff">'.   &Apache::loncommon::start_data_table_header_row().
  '<td rowspan=2 valign="center">&nbsp;<b>No.</b>&nbsp;</td>'.   '<th rowspan="2" valign="middle">'.&mt('No.').'</th>'.
  '<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n";   '<th rowspan="2" valign="middle">'.&nameUserString('header')."</th>\n";
   
     my %scoreptr = (      my %scoreptr = (
     'correct'  =>'correct_by_override',      'correct'  =>'correct_by_override',
     'incorrect'=>'incorrect_by_override',      'incorrect'=>'incorrect_by_override',
Line 3355  sub editgrades { Line 3473  sub editgrades {
     my $header;      my $header;
     while ($ctr < $env{'form.totalparts'}) {      while ($ctr < $env{'form.totalparts'}) {
  my $partid = $env{'form.partid_'.$ctr};   my $partid = $env{'form.partid_'.$ctr};
  push @partid,$partid;   push(@partid,$partid);
  $weight{$partid} = $env{'form.weight_'.$partid};   $weight{$partid} = $env{'form.weight_'.$partid};
  $ctr++;   $ctr++;
     }      }
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     foreach my $partid (@partid) {      foreach my $partid (@partid) {
  $header .= '<td align="center">&nbsp;<b>Old Score</b>&nbsp;</td>'.   $header .= '<th align="center">'.&mt('Old Score').'</th>'.
     '<td align="center">&nbsp;<b>New Score</b>&nbsp;</td>';      '<th align="center">'.&mt('New Score').'</th>';
  $columns{$partid}=2;   $columns{$partid}=2;
  foreach my $stores (@parts) {   foreach my $stores (@parts) {
     my ($part,$type) = &split_part_type($stores);      my ($part,$type) = &split_part_type($stores);
     if ($part !~ m/^\Q$partid\E/) { next;}      if ($part !~ m/^\Q$partid\E/) { next;}
     if ($type eq 'awarded' || $type eq 'solved') { next; }      if ($type eq 'awarded' || $type eq 'solved') { next; }
     my $display=&Apache::lonnet::metadata($url,$stores.'.display');      my $display=&Apache::lonnet::metadata($url,$stores.'.display');
     $display =~ s/\[Part: (\w)+\]//;      $display =~ s/\[Part: \Q$part\E\]//;
     $display =~ s/Number of Attempts/Tries/;              my $narrowtext = &mt('Tries');
     $header .= '<td align="center">&nbsp;<b>Old '.$display.'</b>&nbsp;</td>'.      $display =~ s/Number of Attempts/$narrowtext/;
  '<td align="center">&nbsp;<b>New '.$display.'</b>&nbsp;</td>';      $header .= '<th align="center">'.&mt('Old').' '.$display.'</th>'.
    '<th align="center">'.&mt('New').' '.$display.'</th>';
     $columns{$partid}+=2;      $columns{$partid}+=2;
  }   }
     }      }
     foreach my $partid (@partid) {      foreach my $partid (@partid) {
  my $display_part=&get_display_part($partid,$symb);   my $display_part=&get_display_part($partid,$symb);
  $result .= '<td colspan="'.$columns{$partid}.   $result .= '<th colspan="'.$columns{$partid}.'" align="center">'.
     '" align="center"><b>Part:</b> '.$display_part.      &mt('Part: [_1] (Weight = [_2])',$display_part,$weight{$partid}).
     ' (Weight = '.$weight{$partid}.')</td>';      '</th>';
   
     }      }
     $result .= '</tr><tr bgcolor="#deffff">';      $result .= &Apache::loncommon::end_data_table_header_row().
     $result .= $header;   &Apache::loncommon::start_data_table_header_row().
     $result .= '</tr>'."\n";   $header.
     my $noupdate;   &Apache::loncommon::end_data_table_header_row();
       my @noupdate;
     my ($updateCtr,$noupdateCtr) = (1,1);      my ($updateCtr,$noupdateCtr) = (1,1);
     for ($i=0; $i<$env{'form.total'}; $i++) {      for ($i=0; $i<$env{'form.total'}; $i++) {
  my $line;   my $line;
Line 3398  sub editgrades { Line 3518  sub editgrades {
  my $usec=$classlist->{"$uname:$udom"}[5];   my $usec=$classlist->{"$uname:$udom"}[5];
  if (!&canmodify($usec)) {   if (!&canmodify($usec)) {
     my $numcols=scalar(@partid)*4+2;      my $numcols=scalar(@partid)*4+2;
     $noupdate.=$line."<td colspan=\"$numcols\"><span class=\"LC_warning\">Not allowed to modify student</span></td></tr>";      push(@noupdate,
    $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">".
    &mt('Not allowed to modify student')."</span></td></tr>");
     next;      next;
  }   }
         my %aggregate = ();          my %aggregate = ();
Line 3467  sub editgrades { Line 3589  sub editgrades {
     '<td align="center">'.$awarded.'&nbsp;</td>';      '<td align="center">'.$awarded.'&nbsp;</td>';
     }      }
  }   }
  $line.='</tr>'."\n";   $line.="\n";
   
  my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};   my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
  my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};   my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
Line 3500  sub editgrades { Line 3622  sub editgrades {
  }   }
     }      }
   
     $result.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line;      $result.=&Apache::loncommon::start_data_table_row().
    '<td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line.
    &Apache::loncommon::end_data_table_row();
     $updateCtr++;      $updateCtr++;
  } else {   } else {
     $noupdate.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line;      push(@noupdate,
    '<td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line);
     $noupdateCtr++;      $noupdateCtr++;
  }   }
         if ($aggregateflag) {          if ($aggregateflag) {
Line 3511  sub editgrades { Line 3636  sub editgrades {
   $cdom,$cnum);    $cdom,$cnum);
         }          }
     }      }
     if ($noupdate) {      if (@noupdate) {
 # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;  # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
  my $numcols=scalar(@partid)*4+2;   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 .= &Apache::loncommon::start_data_table_row('LC_empty_row').
     }      '<td align="center" colspan="'.$numcols.'">'.
     $result .= '</table></td></tr></table>'."\n".      &mt('No Changes Occurred For the Students Below').
  &show_grading_menu_form ($symb);      '</td>'.
     my $msg = '<br /><b>Number of records updated = '.$rec_update.      &Apache::loncommon::end_data_table_row();
  ' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'.   foreach my $line (@noupdate) {
  '<b>Total number of students = '.$env{'form.total'}.'</b><br />';      $result.=
    &Apache::loncommon::start_data_table_row().
    $line.
    &Apache::loncommon::end_data_table_row();
    }
       }
       $result .= &Apache::loncommon::end_data_table().
    &show_grading_menu_form($symb);
       my $msg = '<p><b>'.
    &mt('Number of records updated = [_1] for [quant,_2,student].',
       $rec_update,$count).'</b><br />'.
    '<b>'.&mt('Total number of students = [_1]',$env{'form.total'}).
    '</b></p>';
     return $title.$msg.$result;      return $title.$msg.$result;
 }  }
   
Line 3543  sub split_part_type { Line 3680  sub split_part_type {
 #  #
 #--- Javascript to handle 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 $error1=&mt('You need to specify the username or the student/employee ID');
     my $error2=&mt('You need to specify at least one grading field');      my $error2=&mt('You need to specify at least one grading field');
   return(<<ENDPICK);    return(<<ENDPICK);
   function verify(vf) {    function verify(vf) {
Line 3583  ENDPICK Line 3720  ENDPICK
 }  }
   
 sub csvupload_javascript_forward_associate {  sub csvupload_javascript_forward_associate {
     my $error1=&mt('You need to specify the username or ID');      my $error1=&mt('You need to specify the username or the student/employee ID');
     my $error2=&mt('You need to specify at least one grading field');      my $error2=&mt('You need to specify at least one grading field');
   return(<<ENDPICK);    return(<<ENDPICK);
   function verify(vf) {    function verify(vf) {
Line 3666  ENDPICK Line 3803  ENDPICK
 sub csvupload_fields {  sub csvupload_fields {
     my ($symb) = @_;      my ($symb) = @_;
     my (@parts) = &getpartlist($symb);      my (@parts) = &getpartlist($symb);
     my @fields=(['ID','Student ID'],      my @fields=(['ID','Student/Employee ID'],
  ['username','Student Username'],   ['username','Student Username'],
  ['domain','Student Domain']);   ['domain','Student Domain']);
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
Line 3696  ENDPICK Line 3833  ENDPICK
 }  }
   
 sub checkforfile_js {  sub checkforfile_js {
       my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
     my $result =<<CSVFORMJS;      my $result =<<CSVFORMJS;
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
     function checkUpload(formname) {      function checkUpload(formname) {
  if (formname.upfile.value == "") {   if (formname.upfile.value == "") {
     alert("Please use the browse button to select a file from your local directory.");      alert("$alertmsg");
     return false;      return false;
  }   }
  formname.submit();   formname.submit();
Line 3720  sub upcsvScores_form { Line 3858  sub upcsvScores_form {
     $result.=$table;      $result.=$table;
     $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";      $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
     $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";      $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
     $result.='&nbsp;<b>'.&mt('Specify a file containing the class scores for current resource').      $result.='&nbsp;<b>'.&mt('Specify a file containing the class scores for current resource.').
  '.</b></td></tr>'."\n";   '</b></td></tr>'."\n";
     $result.='<tr bgcolor=#ffffe6><td>'."\n";      $result.='<tr bgcolor=#ffffe6><td>'."\n";
     my $upload=&mt("Upload Scores");      my $upload=&mt("Upload Scores");
     my $upfile_select=&Apache::loncommon::upfile_select_html();      my $upfile_select=&Apache::loncommon::upfile_select_html();
Line 3912  sub csvuploadassign { Line 4050  sub csvuploadassign {
                 if ($wgt) {                  if ($wgt) {
                     $entries{$fields{$dest}}=~s/\s//g;                      $entries{$fields{$dest}}=~s/\s//g;
                     my $pcr=$entries{$fields{$dest}} / $wgt;                      my $pcr=$entries{$fields{$dest}} / $wgt;
                     my $award='correct_by_override';                      my $award=($pcr == 0) ? 'incorrect_by_override'
                                             : 'correct_by_override';
                     $grades{"resource.$part.awarded"}=$pcr;                      $grades{"resource.$part.awarded"}=$pcr;
                     $grades{"resource.$part.solved"}=$award;                      $grades{"resource.$part.solved"}=$award;
                     $points{$part}=1;                      $points{$part}=1;
Line 3932  sub csvuploadassign { Line 4071  sub csvuploadassign {
  $grades{$store_key}=$entries{$fields{$dest}};   $grades{$store_key}=$entries{$fields{$dest}};
     }      }
  }   }
  if (! %grades) { push(@skipped,"$username:$domain no data to save"); }   if (! %grades) { 
  $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";             push(@skipped,&mt("[_1]: no data to save","$username:$domain")); 
  my $result=&Apache::lonnet::cstore(\%grades,$symb,          } else {
      $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
      my $result=&Apache::lonnet::cstore(\%grades,$symb,
    $env{'request.course.id'},     $env{'request.course.id'},
    $domain,$username);     $domain,$username);
  if ($result eq 'ok') {     if ($result eq 'ok') {
     $request->print('.');        $request->print('.');
  } else {     } else {
     $request->print("<p>        $request->print("<p><span class=\"LC_error\">".
                               <span class=\"LC_error\">                                &mt("Failed to save data for student [_1]. Message when trying to save was: [_2]",
                                  Failed to save student $username:$domain.                                    "$username:$domain",$result)."</span></p>");
                                  Message when trying to save was ($result)     }
                               </span>     $request->rflush();
                              </p>" );     $countdone++;
  }          }
  $request->rflush();  
  $countdone++;  
     }      }
     $request->print("<br />Saved $countdone students\n");      $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
     if (@skipped) {      if (@skipped) {
  $request->print('<p><h4><b>Skipped Students</b></h4></p>');   $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('No scores stored for the following username(s):'),1).'<br />');
  foreach my $student (@skipped) { $request->print("$student<br />\n"); }          $request->print(join(', ',@skipped));
     }      }
     if (@notallowed) {      if (@notallowed) {
  $request->print('<p><span class="LC_error">Students Not Allowed to Modify</span></p>');   $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Modification of scores not allowed for the following username(s):'),1).'<br />');
  foreach my $student (@notallowed) { $request->print("$student<br />\n"); }   $request->print(join(', ',@notallowed));
     }      }
     $request->print("<br />\n");      $request->print("<br />\n");
     $request->print(&show_grading_menu_form($symb));      $request->print(&show_grading_menu_form($symb));
Line 3973  sub csvuploadassign { Line 4112  sub csvuploadassign {
 sub pickStudentPage {  sub pickStudentPage {
     my ($request) = shift;      my ($request) = shift;
   
       my $alertmsg = &mt('Please select the student you wish to grade.');
     $request->print(<<LISTJAVASCRIPT);      $request->print(<<LISTJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
   
 function checkPickOne(formname) {  function checkPickOne(formname) {
     if (radioSelection(formname.student) == null) {      if (radioSelection(formname.student) == null) {
  alert("Please select the student you wish to grade.");   alert("$alertmsg");
  return;   return;
     }      }
     ptr = pullDownSelection(formname.selectpage);      ptr = pullDownSelection(formname.selectpage);
Line 3996  LISTJAVASCRIPT Line 4136  LISTJAVASCRIPT
     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};      my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
   
     my $result='<h3><span class="LC_info">&nbsp;'.      my $result='<h3><span class="LC_info">&nbsp;'.
  'Manual Grading by Page or Sequence</span></h3>';   &mt('Manual Grading by Page or Sequence').'</span></h3>';
   
     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";      $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
     $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";  
     my ($titles,$symbx) = &getSymbMap();      my ($titles,$symbx) = &getSymbMap();
     my ($curpage) =&Apache::lonnet::decode_symb($symb);       my ($curpage) =&Apache::lonnet::decode_symb($symb); 
 #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb);   #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
 #    my $type=($curpage =~ /\.(page|sequence)/);  #    my $type=($curpage =~ /\.(page|sequence)/);
       my $select = '<select name="selectpage">'."\n";
     my $ctr=0;      my $ctr=0;
     foreach (@$titles) {      foreach (@$titles) {
  my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);   my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
  $result.='<option value="'.$ctr.'" '.   $select.='<option value="'.$ctr.'" '.
     ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').      ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
     '>'.$showtitle.'</option>'."\n";      '>'.$showtitle.'</option>'."\n";
  $ctr++;   $ctr++;
     }      }
     $result.= '</select>'."<br />\n";      $select.= '</select>';
       $result.='&nbsp;<b>'.&mt('Problems from').':</b> '.$select."<br />\n";
   
     $ctr=0;      $ctr=0;
     foreach (@$titles) {      foreach (@$titles) {
  my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);   my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
Line 4023  LISTJAVASCRIPT Line 4165  LISTJAVASCRIPT
     $result.='<input type="hidden" name="page" />'."\n".      $result.='<input type="hidden" name="page" />'."\n".
  '<input type="hidden" name="title" />'."\n";   '<input type="hidden" name="title" />'."\n";
   
     $result.='&nbsp;<b>View Problems Text: </b><label><input type="radio" name="vProb" value="no" checked="checked" /> no </label>'."\n".      my $options =
  '<label><input type="radio" name="vProb" value="yes" /> yes </label>'."<br />\n";   '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
    '<label><input type="radio" name="vProb" value="yes" /> '.&mt('yes').' </label>'."<br />\n";
     $result.='&nbsp;<b>Submission Details: </b>'.      $result.='&nbsp;<b>'.&mt('View Problem Text').': </b>'.$options;
  '<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n".  
  '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> by dates and submissions</label>'."\n".      $options =
  '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n";   '<label><input type="radio" name="lastSub" value="none" /> '.&mt('none').' </label>'."\n".
    '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.&mt('by dates and submissions').'</label>'."\n".
    '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";
       $result.='&nbsp;<b>'.&mt('Submissions').': </b>'.$options;
           
     $result.=&build_section_inputs();      $result.=&build_section_inputs();
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
Line 4038  LISTJAVASCRIPT Line 4183  LISTJAVASCRIPT
  '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".   '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";   '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
   
     $result.='&nbsp;<b>'.&mt('Use CODE:').' </b>'.      $result.='&nbsp;<b>'.&mt('Use CODE').': </b> <input type="text" name="CODE" value="" /> <br />'."\n";
  '<input type="text" name="CODE" value="" /><br />'."\n";  
   
     $result.='&nbsp;<input type="button" '.      $result.='&nbsp;<input type="button" '.
  'onClick="javascript:checkPickOne(this.form);"value="Next->" /><br />'."\n";   'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /><br />'."\n";
   
     $request->print($result);      $request->print($result);
   
     my $studentTable.='&nbsp;<b>Select a student you wish to grade and then click on the Next button.</b><br />'.      my $studentTable.='&nbsp;<b>'.&mt('Select a student you wish to grade and then click on the Next button.').'</b><br />'.
  '<table border="0"><tr><td bgcolor="#777777">'.   &Apache::loncommon::start_data_table().
  '<table border="0"><tr bgcolor="#e6ffff">'.   &Apache::loncommon::start_data_table_header_row().
  '<td align="right">&nbsp;<b>No.</b></td>'.   '<th align="right">&nbsp;'.&mt('No.').'</th>'.
  '<td>'.&nameUserString('header').'</td>'.   '<th>'.&nameUserString('header').'</th>'.
  '<td align="right">&nbsp;<b>No.</b></td>'.   '<th align="right">&nbsp;'.&mt('No.').'</th>'.
  '<td>'.&nameUserString('header').'</td></tr>';   '<th>'.&nameUserString('header').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
     
     my (undef,undef,$fullname) = &getclasslist($getsec,'1');      my (undef,undef,$fullname) = &getclasslist($getsec,'1');
     my $ptr = 1;      my $ptr = 1;
Line 4064  LISTJAVASCRIPT Line 4209  LISTJAVASCRIPT
      return $a cmp $b;       return $a cmp $b;
  } (keys(%$fullname))) {   } (keys(%$fullname))) {
  my ($uname,$udom) = split(/:/,$student);   my ($uname,$udom) = split(/:/,$student);
  $studentTable.=($ptr%2 == 1 ? '<tr bgcolor="#ffffe6">' : '</td>');   $studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
                                     : '</td>');
  $studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';   $studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
  $studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '   $studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
     .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";      .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
  $studentTable.=($ptr%2 == 0 ? '</td></tr>' : '');   $studentTable.=
       ($ptr%2 == 0 ? '</td>'.&Apache::loncommon::end_data_table_row() 
                            : '');
  $ptr++;   $ptr++;
     }      }
     $studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td></tr>' if ($ptr%2 == 0);      if ($ptr%2 == 0) {
     $studentTable.='</table></td></tr></table>'."\n";   $studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td>'.
       &Apache::loncommon::end_data_table_row();
       }
       $studentTable.=&Apache::loncommon::end_data_table()."\n";
     $studentTable.='<input type="button" '.      $studentTable.='<input type="button" '.
  'onClick="javascript:checkPickOne(this.form);"value="Next->" /></form>'."\n";   'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /></form>'."\n";
   
     $studentTable.=&show_grading_menu_form($symb);      $studentTable.=&show_grading_menu_form($symb);
     $request->print($studentTable);      $request->print($studentTable);
Line 4126  sub displayPage { Line 4277  sub displayPage {
     &Apache::lonnet::clear_EXT_cache_status();      &Apache::lonnet::clear_EXT_cache_status();
   
     if (!&canview($usec)) {      if (!&canview($usec)) {
  $request->print('<span class="LC_warning">Unable to view requested student.('.$env{'form.student'}.')</span>');   $request->print('<span class="LC_warning">'.&mt('Unable to view requested student. ([_1])',$env{'form.student'}).'</span>');
  $request->print(&show_grading_menu_form($symb));   $request->print(&show_grading_menu_form($symb));
  return;   return;
     }      }
     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';      my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
     $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom).      $result.='<h3>&nbsp;'.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
  '</h3>'."\n";   '</h3>'."\n";
     if (&Apache::lonnet::validCODE($env{'form.CODE'})) {      $env{'form.CODE'} = uc($env{'form.CODE'});
  $result.='<h3>&nbsp;CODE: '.$env{'form.CODE'}.'</h3>'."\n";      if (&Apache::lonnet::validCODE(uc($env{'form.CODE'}))) {
    $result.='<h3>&nbsp;'.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
     } else {      } else {
  delete($env{'form.CODE'});   delete($env{'form.CODE'});
     }      }
Line 4145  sub displayPage { Line 4297  sub displayPage {
     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});      my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps      my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
     if (!$map) {      if (!$map) {
  $request->print('<span class="LC_warning">Unable to view requested sequence. ('.$resUrl.')</span>');   $request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
  $request->print(&show_grading_menu_form($symb));   $request->print(&show_grading_menu_form($symb));
  return;    return; 
     }      }
Line 4167  sub displayPage { Line 4319  sub displayPage {
     '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";      '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
     }      }
     my $checkIcon = '<img alt="'.&mt('Check Mark').      my $checkIcon = '<img alt="'.&mt('Check Mark').
  '" src="'.$request->dir_config('lonIconsURL').   '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
  '/check.gif" height="16" border="0" />';  
   
     $studentTable.='&nbsp;<b>Note:</b> Problems graded correct by the computer are marked with a '.$checkIcon.      $studentTable.='&nbsp;'.&mt('<b>Note:</b> Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon)."\n".
  ' symbol.'."\n".   &Apache::loncommon::start_data_table().
  '<table border="0"><tr><td bgcolor="#777777">'.   &Apache::loncommon::start_data_table_header_row().
  '<table border="0"><tr bgcolor="#e6ffff">'.   '<th align="center">&nbsp;Prob.&nbsp;</th>'.
  '<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.   '<th>&nbsp;'.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').'</th>'.
  '<td><b>&nbsp;'.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade</b></td></tr>';   &Apache::loncommon::end_data_table_header_row();
   
     &Apache::lonxml::clear_problem_counter();      &Apache::lonxml::clear_problem_counter();
     my ($depth,$question,$prob) = (1,1,1);      my ($depth,$question,$prob) = (1,1,1);
Line 4189  sub displayPage { Line 4340  sub displayPage {
     my $parts = $curRes->parts();      my $parts = $curRes->parts();
             my $title = $curRes->compTitle();              my $title = $curRes->compTitle();
     my $symbx = $curRes->symb();      my $symbx = $curRes->symb();
     $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.      $studentTable.=
  (scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).'&nbsp;parts)').'</td>';   &Apache::loncommon::start_data_table_row().
    '<td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' 
                           : '<br />('.&mt('[_1]&nbsp;parts)',
    scalar(@{$parts}))
    ).
    '</td>';
     $studentTable.='<td valign="top">';      $studentTable.='<td valign="top">';
     my %form = ('CODE' => $env{'form.CODE'},);      my %form = ('CODE' => $env{'form.CODE'},);
     if ($env{'form.vProb'} eq 'yes' ) {      if ($env{'form.vProb'} eq 'yes' ) {
Line 4205  sub displayPage { Line 4362  sub displayPage {
 #    $request->print('match='.$1."<br />\n");  #    $request->print('match='.$1."<br />\n");
 # }  # }
 # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;  # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
  $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>Correct answer:</b><br />'.$companswer;   $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>'.&mt('Correct answer').':</b><br />'.$companswer;
     }      }
   
     my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);      my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
   
     if ($env{'form.lastSub'} eq 'datesub') {      if ($env{'form.lastSub'} eq 'datesub') {
  if ($record{'version'} eq '') {   if ($record{'version'} eq '') {
     $studentTable.='<br />&nbsp;<span class="LC_warning">No recorded submission for this problem</span><br />';      $studentTable.='<br />&nbsp;<span class="LC_warning">'.&mt('No recorded submission for this problem.').'</span><br />';
  } else {   } else {
     my %responseType = ();      my %responseType = ();
     foreach my $partid (@{$parts}) {      foreach my $partid (@{$parts}) {
Line 4248  sub displayPage { Line 4405  sub displayPage {
         $curRes = $iterator->next();          $curRes = $iterator->next();
     }      }
   
     $studentTable.='</table></td></tr></table>'."\n".      $studentTable.='</table>'."\n".
  '<input type="button" value="Save" '.   '<input type="button" value="'.&mt('Save').'" '.
  'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.   'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
  '</form>'."\n";   '</form>'."\n";
     $studentTable.=&show_grading_menu_form($symb);      $studentTable.=&show_grading_menu_form($symb);
Line 4263  sub displaySubByDates { Line 4420  sub displaySubByDates {
     my $isCODE=0;      my $isCODE=0;
     my $isTask = ($symb =~/\.task$/);      my $isTask = ($symb =~/\.task$/);
     if (exists($record->{'resource.CODE'})) { $isCODE=1; }      if (exists($record->{'resource.CODE'})) { $isCODE=1; }
     my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'.      my $studentTable=&Apache::loncommon::start_data_table().
  '<table border="0" width="100%"><tr bgcolor="#e6ffff">'.   &Apache::loncommon::start_data_table_header_row().
  '<td><b>Date/Time</b></td>'.   '<th>'.&mt('Date/Time').'</th>'.
  ($isCODE?'<td><b>CODE</b></td>':'').   ($isCODE?'<th>'.&mt('CODE').'</th>':'').
  '<td><b>Submission</b></td>'.   '<th>'.&mt('Submission').'</th>'.
  '<td><b>Status&nbsp;</b></td></tr>';   '<th>'.&mt('Status').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
     my ($version);      my ($version);
     my %mark;      my %mark;
     my %orders;      my %orders;
     $mark{'correct_by_student'} = $checkIcon;      $mark{'correct_by_student'} = $checkIcon;
     if (!exists($$record{'1:timestamp'})) {      if (!exists($$record{'1:timestamp'})) {
  return '<br />&nbsp;<span class="LC_warning">Nothing submitted - no attempts</span><br />';   return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />';
     }      }
   
     my $interaction;      my $interaction;
       my $no_increment = 1;
     for ($version=1;$version<=$$record{'version'};$version++) {      for ($version=1;$version<=$$record{'version'};$version++) {
  my $timestamp = scalar(localtime($$record{$version.':timestamp'}));   my $timestamp = 
       &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
  if (exists($$record{$version.':resource.0.version'})) {   if (exists($$record{$version.':resource.0.version'})) {
     $interaction = $$record{$version.':resource.0.version'};      $interaction = $$record{$version.':resource.0.version'};
  }   }
   
  my $where = ($isTask ? "$version:resource.$interaction"   my $where = ($isTask ? "$version:resource.$interaction"
              : "$version:resource");               : "$version:resource");
  $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';   $studentTable.=&Apache::loncommon::start_data_table_row().
       '<td>'.$timestamp.'</td>';
  if ($isCODE) {   if ($isCODE) {
     $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';      $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
  }   }
Line 4305  sub displaySubByDates { Line 4466  sub displaySubByDates {
   
     my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)      my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
                : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));                 : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
     $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';      $displaySub[0].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.'&nbsp;';
     $displaySub[0].='<span class="LC_internal_info">(ID&nbsp;'.      $displaySub[0].='<span class="LC_internal_info">('.&mt('ID').'&nbsp;'.
  $responseId.')</span>&nbsp;<b>';   $responseId.')</span>&nbsp;<b>';
     if ($$record{"$where.$partid.tries"} eq '') {      if ($$record{"$where.$partid.tries"} eq '') {
  $displaySub[0].='Trial&nbsp;not&nbsp;counted';   $displaySub[0].=&mt('Trial&nbsp;not&nbsp;counted');
     } else {      } else {
  $displaySub[0].='Trial&nbsp;'.   $displaySub[0].=&mt('Trial&nbsp;[_1]',
     $$record{"$where.$partid.tries"};      $$record{"$where.$partid.tries"});
     }      }
     my $responseType=($isTask ? 'Task'      my $responseType=($isTask ? 'Task'
                                               : $responseType->{$partid}->{$responseId});                                                : $responseType->{$partid}->{$responseId});
     if (!exists($orders{$partid})) { $orders{$partid}={}; }      if (!exists($orders{$partid})) { $orders{$partid}={}; }
     if (!exists($orders{$partid}->{$responseId})) {      if (!exists($orders{$partid}->{$responseId})) {
  $orders{$partid}->{$responseId}=   $orders{$partid}->{$responseId}=
     &get_order($partid,$responseId,$symb,$uname,$udom);      &get_order($partid,$responseId,$symb,$uname,$udom,
                                          $no_increment);
     }      }
     $displaySub[0].='</b>&nbsp; '.      $displaySub[0].='</b>&nbsp; '.
  &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />';   &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />';
  }   }
     }      }
     if (exists($$record{"$where.$partid.checkedin"})) {      if (exists($$record{"$where.$partid.checkedin"})) {
  $displaySub[1].='Checked in by '.   $displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
     $$record{"$where.$partid.checkedin"}.' into slot '.      $$record{"$where.$partid.checkedin"},
     $$record{"$where.$partid.checkedin.slot"}.      $$record{"$where.$partid.checkedin.slot"}).
     '<br />';   '<br />';
     }      }
     if (exists $$record{"$where.$partid.award"}) {      if (exists $$record{"$where.$partid.award"}) {
  $displaySub[1].='<b>Part:</b>&nbsp;'.$display_part.' &nbsp;'.   $displaySub[1].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.' &nbsp;'.
     lc($$record{"$where.$partid.award"}).' '.      lc($$record{"$where.$partid.award"}).' '.
     $mark{$$record{"$where.$partid.solved"}}.      $mark{$$record{"$where.$partid.solved"}}.
     '<br />';      '<br />';
Line 4352  sub displaySubByDates { Line 4514  sub displaySubByDates {
  }   }
  $studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];   $studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
  if ($displaySub[2]) {   if ($displaySub[2]) {
     $studentTable.='Manually graded by '.$displaySub[2];      $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
  }   }
  $studentTable.='&nbsp;</td></tr>';   $studentTable.='&nbsp;</td>'.
           &Apache::loncommon::end_data_table_row();
     }      }
     $studentTable.='</table></td></tr></table>';      $studentTable.=&Apache::loncommon::end_data_table();
     return $studentTable;      return $studentTable;
 }  }
   
Line 4372  sub updateGradeByPage { Line 4534  sub updateGradeByPage {
     my ($uname,$udom) = split(/:/,$env{'form.student'});      my ($uname,$udom) = split(/:/,$env{'form.student'});
     my $usec=$classlist->{$env{'form.student'}}[5];      my $usec=$classlist->{$env{'form.student'}}[5];
     if (!&canmodify($usec)) {      if (!&canmodify($usec)) {
  $request->print('<span class="LC_warning">Unable to modify requested student.('.$env{'form.student'}.'</span>');   $request->print('<span class="LC_warning">'.&mt('Unable to modify requested student ([_1])',$env{'form.student'}).'</span>');
  $request->print(&show_grading_menu_form($env{'form.symb'}));   $request->print(&show_grading_menu_form($env{'form.symb'}));
  return;   return;
     }      }
     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';      my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
     $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).      $result.='<h3>&nbsp;'.&mt('Student: ').&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
  '</h3>'."\n";   '</h3>'."\n";
   
     $request->print($result);      $request->print($result);
Line 4386  sub updateGradeByPage { Line 4548  sub updateGradeByPage {
     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});      my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps      my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
     if (!$map) {      if (!$map) {
  $request->print('<span class="LC_warning">Unable to grade requested sequence. ('.$resUrl.')</span>');   $request->print('<span class="LC_warning">'.&mt('Unable to grade requested sequence ([_1]).',$resUrl).'</span>');
  my ($symb)=&get_symb($request);   my ($symb)=&get_symb($request);
  $request->print(&show_grading_menu_form($symb));   $request->print(&show_grading_menu_form($symb));
  return;    return; 
Line 4394  sub updateGradeByPage { Line 4556  sub updateGradeByPage {
     my $iterator = $navmap->getIterator($map->map_start(),      my $iterator = $navmap->getIterator($map->map_start(),
  $map->map_finish());   $map->map_finish());
   
     my $studentTable='<table border="0"><tr><td bgcolor="#777777">'.      my $studentTable=
  '<table border="0"><tr bgcolor="#e6ffff">'.   &Apache::loncommon::start_data_table().
  '<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.   &Apache::loncommon::start_data_table_header_row().
  '<td><b>&nbsp;Title&nbsp;</b></td>'.   '<th align="center">&nbsp;'.&mt('Prob.').'&nbsp;</th>'.
  '<td><b>&nbsp;Previous Score&nbsp;</b></td>'.   '<th>&nbsp;'.&mt('Title').'&nbsp;</th>'.
  '<td><b>&nbsp;New Score&nbsp;</b></td></tr>';   '<th>&nbsp;'.&mt('Previous Score').'&nbsp;</th>'.
    '<th>&nbsp;'.&mt('New Score').'&nbsp;</th>'.
    &Apache::loncommon::end_data_table_header_row();
   
     $iterator->next(); # skip the first BEGIN_MAP      $iterator->next(); # skip the first BEGIN_MAP
     my $curRes = $iterator->next(); # for "current resource"      my $curRes = $iterator->next(); # for "current resource"
Line 4412  sub updateGradeByPage { Line 4576  sub updateGradeByPage {
     my $parts = $curRes->parts();      my $parts = $curRes->parts();
             my $title = $curRes->compTitle();              my $title = $curRes->compTitle();
     my $symbx = $curRes->symb();      my $symbx = $curRes->symb();
     $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.      $studentTable.=
  (scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).'&nbsp;parts)').'</td>';   &Apache::loncommon::start_data_table_row().
    '<td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' 
                                           : '<br />('.&mt('[quant,_1,&nbsp;part]',scalar(@{$parts}))
    .')').'</td>';
     $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';      $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
   
     my %newrecord=();      my %newrecord=();
Line 4457  sub updateGradeByPage { Line 4625  sub updateGradeByPage {
  }   }
  my $display_part=&get_display_part($partid,$curRes->symb());   my $display_part=&get_display_part($partid,$curRes->symb());
  my $oldstatus = $env{'form.solved'.$question.'_'.$partid};   my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
  $displayPts[0].='&nbsp;<b>Part:</b> '.$display_part.' = '.   $displayPts[0].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
     (($oldstatus eq 'excused') ? 'excused' : $oldpts).      (($oldstatus eq 'excused') ? 'excused' : $oldpts).
     '&nbsp;<br />';      '&nbsp;<br />';
  $displayPts[1].='&nbsp;<b>Part:</b> '.$display_part.' = '.   $displayPts[1].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
      (($score eq 'excused') ? 'excused' : $newpts).       (($score eq 'excused') ? 'excused' : $newpts).
     '&nbsp;<br />';      '&nbsp;<br />';
  $question++;   $question++;
Line 4500  sub updateGradeByPage { Line 4668  sub updateGradeByPage {
   
     $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.      $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
  '<td valign="top">'.$displayPts[1].'</td>'.   '<td valign="top">'.$displayPts[1].'</td>'.
  '</tr>';   &Apache::loncommon::end_data_table_row();
   
     $prob++;      $prob++;
  }   }
         $curRes = $iterator->next();          $curRes = $iterator->next();
     }      }
   
     $studentTable.='</td></tr></table></td></tr></table>';      $studentTable.=&Apache::loncommon::end_data_table();
     $studentTable.=&show_grading_menu_form($env{'form.symb'});      $studentTable.=&show_grading_menu_form($env{'form.symb'});
     my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :      my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
   'The scores were changed for '.    &mt('The scores were changed for [quant,_1,problem].',
   $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));    $changeflag));
     $request->print($grademsg.$studentTable);      $request->print($grademsg.$studentTable);
   
     return '';      return '';
Line 4548  Next each scanline is checked for any er Line 4716  Next each scanline is checked for any er
 bubbles' (it's an error because it may have been mis-scanned  bubbles' (it's an error because it may have been mis-scanned
 because too light bubbling), 'double bubble' (each bubble line should  because too light bubbling), 'double bubble' (each bubble line should
 have no more that one letter picked), invalid or duplicated CODE,  have no more that one letter picked), invalid or duplicated CODE,
 invalid student ID  invalid student/employee ID
   
 If the CODE option is used that determines the randomization of the  If the CODE option is used that determines the randomization of the
 homework problems, either way the student ID is looked up into a  homework problems, either way the student/employee ID is looked up into a
 username:domain.  username:domain.
   
 During the validation phase the instructor can choose to skip scanlines.   During the validation phase the instructor can choose to skip scanlines. 
Line 4620  sub getSequenceDropDown { Line 4788  sub getSequenceDropDown {
     return $result;      return $result;
 }  }
   
   my %bubble_lines_per_response;     # no. bubble lines for each response.
                                      # key is zero-based index - 0, 1, 2 ...
   
   my %first_bubble_line;             # First bubble line no. for each bubble.
   
   my %subdivided_bubble_lines;       # no. bubble lines for optionresponse, 
                                      # matchresponse or rankresponse, where 
                                      # an individual response can have multiple 
                                      # lines
   
   my %responsetype_per_response;     # responsetype for each response
   
   # Save and restore the bubble lines array to the form env.
   
   
   sub save_bubble_lines {
       foreach my $line (keys(%bubble_lines_per_response)) {
    $env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
    $env{"form.scantron.first_bubble_line.$line"} =
       $first_bubble_line{$line};
           $env{"form.scantron.sub_bubblelines.$line"} = 
               $subdivided_bubble_lines{$line};
           $env{"form.scantron.responsetype.$line"} =
               $responsetype_per_response{$line};
       }
   }
   
   
   sub restore_bubble_lines {
       my $line = 0;
       %bubble_lines_per_response = ();
       while ($env{"form.scantron.bubblelines.$line"}) {
    my $value = $env{"form.scantron.bubblelines.$line"};
    $bubble_lines_per_response{$line} = $value;
    $first_bubble_line{$line}  =
       $env{"form.scantron.first_bubble_line.$line"};
           $subdivided_bubble_lines{$line} =
               $env{"form.scantron.sub_bubblelines.$line"};
           $responsetype_per_response{$line} =
               $env{"form.scantron.responsetype.$line"};
    $line++;
       }
   }
   
   #  Given the parsed scanline, get the response for 
   #  'answer' number n:
   
   sub get_response_bubbles {
       my ($parsed_line, $response)  = @_;
   
       my $bubble_line = $first_bubble_line{$response-1} +1;
       my $bubble_lines= $bubble_lines_per_response{$response-1};
       
       my $selected = "";
   
       for (my $bline = 0; $bline < $bubble_lines; $bline++) {
    $selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":";
    $bubble_line++;
       }
       return $selected;
   }
   
 =pod   =pod 
   
Line 4632  sub getSequenceDropDown { Line 4861  sub getSequenceDropDown {
 sub scantron_filenames {  sub scantron_filenames {
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $getpropath = 1;
     my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,      my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
     &propath($cdom,$cname));                                         $getpropath);
     my @possiblenames;      my @possiblenames;
     foreach my $filename (sort(@files)) {      foreach my $filename (sort(@files)) {
  ($filename)=split(/&/,$filename);   ($filename)=split(/&/,$filename);
Line 4676  sub scantron_uploads { Line 4906  sub scantron_uploads {
 =cut  =cut
   
 sub scantron_scantab {  sub scantron_scantab {
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');  
     my $result='<select name="scantron_format">'."\n";      my $result='<select name="scantron_format">'."\n";
     $result.='<option></option>'."\n";      $result.='<option></option>'."\n";
     foreach my $line (<$fh>) {      my @lines = &get_scantronformat_file();
  my ($name,$descrip)=split(/:/,$line);      if (@lines > 0) {
  if ($name =~ /^\#/) { next; }          foreach my $line (@lines) {
  $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";              next if (($line =~ /^\#/) || ($line eq ''));
       my ($name,$descrip)=split(/:/,$line);
       $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
           }
     }      }
     $result.='</select>'."\n";      $result.='</select>'."\n";
   
     return $result;      return $result;
 }  }
   
   =pod
   
   =item get_scantronformat_file
   
     Returns an array containing lines from the scantron format file for
     the domain of the course.
   
     If a url for a custom.tab file is listed in domain's configuration.db, 
     lines are from this file.
   
     Otherwise, if a default.tab has been published in RES space by the 
     domainconfig user, lines are from this file.
   
     Otherwise, fall back to getting lines from the legacy file on the
     local server:  /home/httpd/lonTabs/default_scantronformat.tab    
   
   =cut
   
   sub get_scantronformat_file {
       my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
       my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$cdom);
       my $gottab = 0;
       my @lines;
       if (ref($domconfig{'scantron'}) eq 'HASH') {
           if ($domconfig{'scantron'}{'scantronformat'} ne '') {
               my $formatfile = &Apache::lonnet::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.$domconfig{'scantron'}{'scantronformat'});
               if ($formatfile ne '-1') {
                   @lines = split("\n",$formatfile,-1);
                   $gottab = 1;
               }
           }
       }
       if (!$gottab) {
           my $confname = $cdom.'-domainconfig';
           my $default = $Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$cdom.'/'.$confname.'/default.tab';
           my $formatfile =  &Apache::lonnet::getfile($default);
           if ($formatfile ne '-1') {
               @lines = split("\n",$formatfile,-1);
               $gottab = 1;
           }
       }
       if (!$gottab) {
           my @domains = &Apache::lonnet::current_machine_domains();
           if (grep(/^\Q$cdom\E$/,@domains)) {
               my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
               @lines = <$fh>;
               close($fh);
           } else {
               my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab');
               @lines = <$fh>;
               close($fh);
           }
       }
       return @lines;
   }
   
 =pod   =pod 
   
 =item scantron_CODElist  =item scantron_CODElist
Line 4721  sub scantron_CODElist { Line 5008  sub scantron_CODElist {
 =cut  =cut
   
 sub scantron_CODEunique {  sub scantron_CODEunique {
     my $result='<span style="white-space: nowrap;">      my $result='<span class="LC_nobreak">
                  <label><input type="radio" name="scantron_CODEunique"                   <label><input type="radio" name="scantron_CODEunique"
                         value="yes" checked="checked" />'.&mt('Yes').' </label>                          value="yes" checked="checked" />'.&mt('Yes').' </label>
                 </span>                  </span>
                 <span style="white-space: nowrap;">                  <span class="LC_nobreak">
                  <label><input type="radio" name="scantron_CODEunique"                   <label><input type="radio" name="scantron_CODEunique"
                         value="no" />'.&mt('No').' </label>                          value="no" />'.&mt('No').' </label>
                 </span>';                  </span>';
Line 4762  sub scantron_selectphase { Line 5049  sub scantron_selectphase {
     my $CODE_unique=&scantron_CODEunique();      my $CODE_unique=&scantron_CODEunique();
     my $result;      my $result;
   
       $ssi_error = 0;
   
     # Chunk of form to prompt for a file to grade and how:      # Chunk of form to prompt for a file to grade and how:
   
     $result.= <<SCANTRONFORM;      $result.= '
     <table width="100%" border="0">      <br />
     <tr>      <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
      <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">      <input type="hidden" name="command" value="scantron_warning" />
       <td bgcolor="#777777">      '.$default_form_data.'
        <input type="hidden" name="command" value="scantron_warning" />      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
         $default_form_data         '.&Apache::loncommon::start_data_table_header_row().'
         <table width="100%" border="0">              <th colspan="2">
           <tr bgcolor="#e6ffff">                &nbsp;'.&mt('Specify file and which Folder/Sequence to grade').'
             <td colspan="2">              </th>
               &nbsp;<b>Specify file and which Folder/Sequence to grade</b>         '.&Apache::loncommon::end_data_table_header_row().'
             </td>         '.&Apache::loncommon::start_data_table_row().'
           </tr>              <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>
           <tr bgcolor="#ffffe6">         '.&Apache::loncommon::end_data_table_row().'
             <td> Sequence to grade: </td><td> $sequence_selector </td>         '.&Apache::loncommon::start_data_table_row().'
           </tr>              <td> '.&mt('Filename of bubblesheet data file:').' </td><td> '.$file_selector.' </td>
           <tr bgcolor="#ffffe6">         '.&Apache::loncommon::end_data_table_row().'
             <td> Filename of scoring office file: </td><td> $file_selector </td>         '.&Apache::loncommon::start_data_table_row().'
           </tr>              <td> '.&mt('Format of bubblesheet data file:').' </td><td> '.$format_selector.' </td>
           <tr bgcolor="#ffffe6">         '.&Apache::loncommon::end_data_table_row().'
             <td> Format of data file: </td><td> $format_selector </td>         '.&Apache::loncommon::start_data_table_row().'
           </tr>              <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>
           <tr bgcolor="#ffffe6">         '.&Apache::loncommon::end_data_table_row().'
             <td> Saved CODEs to validate against: </td><td> $CODE_selector</td>         '.&Apache::loncommon::start_data_table_row().'
           </tr>              <td> '.&mt('Each CODE is only to be used once:').'</td><td> '.$CODE_unique.' </td>
           <tr bgcolor="#ffffe6">         '.&Apache::loncommon::end_data_table_row().'
             <td> Each CODE is only to be used once:</td><td> $CODE_unique </td>         '.&Apache::loncommon::start_data_table_row().'
           </tr>      <td> '.&mt('Options:').' </td>
           <tr bgcolor="#ffffe6">  
     <td> Options: </td>  
             <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_redo" value="redo_skipped"/> '.&mt('Do only previously skipped records').'</label> <br />
                <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all existing corrections</label> <br />                 <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> '.&mt('Remove all existing corrections').'</label> <br />
                <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label>                 <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources when grading').'</label>
     </td>      </td>
           </tr>         '.&Apache::loncommon::end_data_table_row().'
           <tr bgcolor="#ffffe6">         '.&Apache::loncommon::start_data_table_row().'
             <td colspan="2">              <td colspan="2">
               <input type="submit" value="Grading: Validate Scantron Records" />                <input type="submit" value="'.&mt('Grading: Validate Bubblesheet Records').'" />
             </td>              </td>
           </tr>         '.&Apache::loncommon::end_data_table_row().'
         </table>      '.&Apache::loncommon::end_data_table().'
        </td>      </form>
      </form>  ';
     </tr>  
 SCANTRONFORM  
         
     $r->print($result);      $r->print($result);
   
Line 4818  SCANTRONFORM Line 5103  SCANTRONFORM
   
  # Chunk of form to prompt for a scantron file upload.   # Chunk of form to prompt for a scantron file upload.
   
         $r->print(<<SCANTRONFORM);          $r->print('
     <tr>      <br />
       <td bgcolor="#777777">      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
         <table width="100%" border="0">         '.&Apache::loncommon::start_data_table_header_row().'
           <tr bgcolor="#e6ffff">              <th>
                 &nbsp;'.&mt('Specify a bubblesheet data file to upload.').'
               </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
             <td>              <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 $default_form_data=&defaultFormData(&get_symb($r,1));
     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};      my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
     $r->print(<<UPLOAD);      $r->print('
               <script type="text/javascript" language="javascript">                <script type="text/javascript" language="javascript">
     function checkUpload(formname) {      function checkUpload(formname) {
  if (formname.upfile.value == "") {   if (formname.upfile.value == "") {
     alert("Please use the browse button to select a file from your local directory.");      alert("'.&mt('Please use the browse button to select a file from your local directory.').'");
     return false;      return false;
  }   }
  formname.submit();   formname.submit();
     }      }
               </script>                </script>
   
               <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>                <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
                 $default_form_data                  '.$default_form_data.'
                 <input name='courseid' type='hidden' value='$cnum' />                  <input name="courseid" type="hidden" value="'.$cnum.'" />
                 <input name='domainid' type='hidden' value='$cdom' />                  <input name="domainid" type="hidden" value="'.$cdom.'" />
                 <input name='command' value='scantronupload_save' type='hidden' />                  <input name="command" value="scantronupload_save" type="hidden" />
                 File to upload:<input type="file" name="upfile" size="50" />                  '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'
                 <br />                  <br />
                 <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />                  <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
               </form>                </form>
 UPLOAD  ');
   
         $r->print(<<SCANTRONFORM);          $r->print('
             </td>              </td>
           </tr>         '.&Apache::loncommon::end_data_table_row().'
         </table>         '.&Apache::loncommon::end_data_table().'
       </td>  ');
     </tr>  
 SCANTRONFORM  
     }      }
   
     # Chunk of the form that prompts to view a scoring office file,      # Chunk of the form that prompts to view a scoring office file,
     # corrected file, skipped records in a file.      # corrected file, skipped records in a file.
   
     $r->print(<<SCANTRONFORM);      $r->print('
     <tr>     <br />
       <form action='/adm/grades' name='scantron_download'>     <form action="/adm/grades" name="scantron_download">
         <td bgcolor="#777777">       '.$default_form_data.'
   $default_form_data       <input type="hidden" name="command" value="scantron_download" />
           <input type="hidden" name="command" value="scantron_download" />       '.&Apache::loncommon::start_data_table('LC_scantron_action').'
           <table width="100%" border="0">         '.&Apache::loncommon::start_data_table_header_row().'
             <tr bgcolor="#e6ffff">                <th>
               <td colspan="2">                  &nbsp;'.&mt('Download a scoring office file').'
                 &nbsp;<b>Download a scoring office file</b>                </th>
               </td>         '.&Apache::loncommon::end_data_table_header_row().'
             </tr>         '.&Apache::loncommon::start_data_table_row().'
             <tr bgcolor="#ffffe6">                <td> '.&mt('Filename of scoring office file: [_1]',$file_selector).' 
               <td> Filename of scoring office file: </td><td> $file_selector </td>                  <br />
             </tr>                  <input type="submit" value="'.&mt('Download: Show List of Associated Files').'" />
             <tr bgcolor="#ffffe6">         '.&Apache::loncommon::end_data_table_row().'
               <td colspan="2">       '.&Apache::loncommon::end_data_table().'
                 <input type="submit" value="Download: Show List of Associated Files" />     </form>
               </td>     <br />
             </tr>  ');
           </table>  
         </td>  
       </form>  
     </tr>  
 SCANTRONFORM  
   
     $r->print('<tr><td bgcolor="#777777">');  
     &Apache::lonpickcode::code_list($r,2);      &Apache::lonpickcode::code_list($r,2);
     $r->print('</td></tr></table>');  
       $r->print('<br /><form method="post" name="checkscantron">'.
                $default_form_data."\n".
                &Apache::loncommon::start_data_table('LC_scantron_action')."\n".
                &Apache::loncommon::start_data_table_header_row()."\n".
                '<th colspan="2">
                 &nbsp;'.&mt('Review bubblesheet data and submissions for a previously graded folder/sequence')."\n".
                '</th>'."\n".
                 &Apache::loncommon::end_data_table_header_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td> '.&mt('Graded folder/sequence:').' </td>'."\n".
                 '<td> '.$sequence_selector.' </td>'.
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td> '.&mt('Filename of scoring office file:').' </td>'."\n".
                 '<td> '.$file_selector.' </td>'."\n".
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td> '.&mt('Format of data file:').' </td>'."\n".
                 '<td> '.$format_selector.' </td>'."\n".
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td> '.&mt('Options').' </td>'."\n".
                 '<td> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources').'</label></td>'.
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td colspan="2">'."\n".
                 '<input type="hidden" name="command" value="checksubmissions" />'."\n".
                 '<input type="submit" value="'.&mt('Review Scantron Data and Submission Records').'" />'."\n".
                 '</td>'."\n".
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::end_data_table()."\n".
                 '</form><br />');
     $r->print($grading_menu_button);      $r->print($grading_menu_button);
     return      return;
 }  }
   
 =pod  =pod
Line 4932  SCANTRONFORM Line 5240  SCANTRONFORM
       CODEstart   - (only matter if a CODE exists) column in the line where        CODEstart   - (only matter if a CODE exists) column in the line where
                      the CODE starts                       the CODE starts
       CODElength  - length of the CODE        CODElength  - length of the CODE
       IDstart     - column where the student ID number starts        IDstart     - column where the student/employee ID starts
       IDlength    - length of the student ID info        IDlength    - length of the student/employee ID info
       Qstart      - column where the information from the bubbled        Qstart      - column where the information from the bubbled
                     'questions' start                      'questions' start
       Qlength     - number of columns comprising a single bubble line from        Qlength     - number of columns comprising a single bubble line from
Line 4959  SCANTRONFORM Line 5267  SCANTRONFORM
   
 sub get_scantron_config {  sub get_scantron_config {
     my ($which) = @_;      my ($which) = @_;
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');      my @lines = &get_scantronformat_file();
     my %config;      my %config;
     #FIXME probably should move to XML it has already gotten a bit much now      #FIXME probably should move to XML it has already gotten a bit much now
     foreach my $line (<$fh>) {      foreach my $line (@lines) {
  my ($name,$descrip)=split(/:/,$line);   my ($name,$descrip)=split(/:/,$line);
  if ($name ne $which ) { next; }   if ($name ne $which ) { next; }
  chomp($line);   chomp($line);
Line 4975  sub get_scantron_config { Line 5283  sub get_scantron_config {
  $config{'IDstart'}=$config[5];   $config{'IDstart'}=$config[5];
  $config{'IDlength'}=$config[6];   $config{'IDlength'}=$config[6];
  $config{'Qstart'}=$config[7];   $config{'Qstart'}=$config[7];
  $config{'Qlength'}=$config[8];    $config{'Qlength'}=$config[8];
  $config{'Qoff'}=$config[9];   $config{'Qoff'}=$config[9];
  $config{'Qon'}=$config[10];   $config{'Qon'}=$config[10];
  $config{'PaperID'}=$config[11];   $config{'PaperID'}=$config[11];
Line 4993  sub get_scantron_config { Line 5301  sub get_scantron_config {
   
 =item username_to_idmap  =item username_to_idmap
   
     creates a hash keyed by student id with values of the corresponding      creates a hash keyed by student/employee ID with values of the corresponding
     student username:domain.      student username:domain.
   
   Arguments:    Arguments:
Line 5032  sub username_to_idmap { Line 5340  sub username_to_idmap {
     $whichline         - line number of the passed in scanline      $whichline         - line number of the passed in scanline
     $field             - type of change to process       $field             - type of change to process 
                          (either                            (either 
                           'ID'     -> correct the student ID number                            'ID'     -> correct the student/employee ID
                           'CODE'   -> correct the CODE                            'CODE'   -> correct the CODE
                           'answer' -> fixup the submitted answers)                            'answer' -> fixup the submitted answers)
           
Line 5049  sub username_to_idmap { Line 5357  sub username_to_idmap {
                           - 'answer'                            - 'answer'
                                'response' - new answer or 'none' if blank                                 'response' - new answer or 'none' if blank
                                'question' - the bubble line to change                                 'question' - the bubble line to change
                                  'questionnum' - the question identifier,
                                                  may include subquestion. 
   
   Returns:    Returns:
     $line - the modified scanline      $line - the modified scanline
Line 5061  sub username_to_idmap { Line 5371  sub username_to_idmap {
   
 sub scantron_fixup_scanline {  sub scantron_fixup_scanline {
     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;      my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
   
     if ($field eq 'ID') {      if ($field eq 'ID') {
  if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {   if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
     return ($line,1,'New value too large');      return ($line,1,'New value too large');
Line 5098  sub scantron_fixup_scanline { Line 5407  sub scantron_fixup_scanline {
  my $answer=${off}x$length;   my $answer=${off}x$length;
  if ($args->{'response'} eq 'none') {   if ($args->{'response'} eq 'none') {
     &scan_data($scan_data,      &scan_data($scan_data,
        "$whichline.no_bubble.".$args->{'question'},'1');         "$whichline.no_bubble.".$args->{'questionnum'},'1');
  } else {   } else {
     if ($on eq 'letter') {      if ($on eq 'letter') {
  my @alphabet=('A'..'Z');   my @alphabet=('A'..'Z');
Line 5110  sub scantron_fixup_scanline { Line 5419  sub scantron_fixup_scanline {
  substr($answer,$args->{'response'},1)=$on;   substr($answer,$args->{'response'},1)=$on;
     }      }
     &scan_data($scan_data,      &scan_data($scan_data,
        "$whichline.no_bubble.".$args->{'question'},undef,'1');         "$whichline.no_bubble.".$args->{'questionnum'},undef,'1');
  }   }
  my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};   my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
  substr($line,$where-1,$length)=$answer;   substr($line,$where-1,$length)=$answer;
Line 5147  sub scan_data { Line 5456  sub scan_data {
     return $scan_data->{$filename.'_'.$key};      return $scan_data->{$filename.'_'.$key};
 }  }
   
   # ----- These first few routines are general use routines.----
   
   # Return the number of occurences of a pattern in a string.
   
   sub occurence_count {
       my ($string, $pattern) = @_;
   
       my @matches = ($string =~ /$pattern/g);
   
       return scalar(@matches);
   }
   
   
   # Take a string known to have digits and convert all the
   # digits into letters in the range J,A..I.
   
   sub digits_to_letters {
       my ($input) = @_;
   
       my @alphabet = ('J', 'A'..'I');
   
       my @input    = split(//, $input);
       my $output ='';
       for (my $i = 0; $i < scalar(@input); $i++) {
    if ($input[$i] =~ /\d/) {
       $output .= $alphabet[$input[$i]];
    } else {
       $output .= $input[$i];
    }
       }
       return $output;
   }
   
 =pod   =pod 
   
 =item scantron_parse_scanline  =item scantron_parse_scanline
Line 5172  sub scan_data { Line 5514  sub scan_data {
        CODE_ignore_dup - 1 if the CODE is a duplicated use when unique         CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
                             CODEs were selected, but the usage has been                              CODEs were selected, but the usage has been
                             forced by the operator                              forced by the operator
        ID  - student ID         ID  - student/employee ID
        PaperID - if used, the ID number printed on the sheet when the          PaperID - if used, the ID number printed on the sheet when the 
                  paper was scanned                   paper was scanned
        FirstName - first name from the sheet         FirstName - first name from the sheet
Line 5206  sub scan_data { Line 5548  sub scan_data {
   
 sub scantron_parse_scanline {  sub scantron_parse_scanline {
     my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;      my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
   
     my %record;      my %record;
     my $questions=substr($line,$$scantron_config{'Qstart'}-1);  # Answers      my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
       my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos);  # Answers
     my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff      my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
     if (!($$scantron_config{'CODElocation'} eq 0 ||      if (!($$scantron_config{'CODElocation'} eq 0 ||
   $$scantron_config{'CODElocation'} eq 'none')) {    $$scantron_config{'CODElocation'} eq 'none')) {
Line 5244  sub scantron_parse_scanline { Line 5588  sub scantron_parse_scanline {
     my $questnum=0;      my $questnum=0;
     my $ansnum  =1; # Multiple 'answer lines'/question.      my $ansnum  =1; # Multiple 'answer lines'/question.
   
     while ($questions) {      chomp($questions); # Get rid of any trailing \n.
       $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
       while (length($questions)) {
  my $answers_needed = $bubble_lines_per_response{$questnum};   my $answers_needed = $bubble_lines_per_response{$questnum};
  my $answer_length  = $$scantron_config{'Qlength'} * $answers_needed;          my $answer_length  = ($$scantron_config{'Qlength'} * $answers_needed)
                                || 1;
           $questnum++;
           my $quest_id = $questnum;
  $questnum++;          my $currentquest = substr($questions,0,$answer_length);
  my $currentquest = substr($questions,0,$answer_length);          $questions       = substr($questions,$answer_length);
  $questions       = substr($questions,0,$answer_length)='';          if (length($currentquest) < $answer_length) { next; }
  if (length($currentquest) < $answer_length) { next; }  
           if ($subdivided_bubble_lines{$questnum-1} =~ /,/) {
  # Qon letter implies for each slot in currentquest we have:              my $subquestnum = 1;
  #    ? or * for doubles a letter in A-Z for a bubble and              my $subquestions = $currentquest;
         #    about anything else (esp. a value of Qoff for missing              my @subanswers_needed = 
  #    bubbles.                  split(/,/,$subdivided_bubble_lines{$questnum-1});  
               foreach my $subans (@subanswers_needed) {
                   my $subans_length =
  if ($$scantron_config{'Qon'} eq 'letter') {                      ($$scantron_config{'Qlength'} * $subans)  || 1;
                   my $currsubquest = substr($subquestions,0,$subans_length);
     if ($currentquest =~ /\?/                  $subquestions   = substr($subquestions,$subans_length);
  || $currentquest =~ /\*/                  $quest_id = "$questnum.$subquestnum";
  || (&occurence_count($currentquest, "[A-Z]") > 1)) {                  if (($$scantron_config{'Qon'} eq 'letter') ||
  push(@{$record{'scantron.doubleerror'}},$questnum);                      ($$scantron_config{'Qon'} eq 'number')) {
  for (my $ans = 0; $ans < $answers_needed; $ans++) {                       $ansnum = &scantron_validator_lettnum($ansnum, 
     my $bubble = substr($currentquest, $ans, 1);                          $questnum,$quest_id,$subans,$currsubquest,$whichline,
     if ($bubble =~ /[A-Z]/ ) {                          \@alphabet,\%record,$scantron_config,$scan_data);
  $record{"scantron.$ansnum.answer"} = $bubble;                  } else {
     } else {                      $ansnum = &scantron_validator_positional($ansnum,
  $record{"scantron.$ansnum.answer"}='';                          $questnum,$quest_id,$subans,$currsubquest,$whichline,                        \@alphabet,\%record,$scantron_config,$scan_data);
     }                  }
     $ansnum++;                  $subquestnum ++;
  }              }
           } else {
     } elsif (!defined($currentquest)              if (($$scantron_config{'Qon'} eq 'letter') ||
      || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest))                  ($$scantron_config{'Qon'} eq 'number')) {
      || (&occurence_count($currentquest, "[A-Z]") == 0)) {                  $ansnum = &scantron_validator_lettnum($ansnum,$questnum,
  for (my $ans = 0; $ans < $answers_needed; $ans++ ) {                      $quest_id,$answers_needed,$currentquest,$whichline,
     $record{"scantron.$ansnum.answer"}='';                      \@alphabet,\%record,$scantron_config,$scan_data);
     $ansnum++;              } else {
                   $ansnum = &scantron_validator_positional($ansnum,$questnum,
  }                      $quest_id,$answers_needed,$currentquest,$whichline,
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {                      \@alphabet,\%record,$scantron_config,$scan_data);
     push(@{$record{"scantron.missingerror"}},$questnum);              }
     $ansnum += $answers_needed;          }
  }      }
       $record{'scantron.maxquest'}=$questnum;
     } else {      return \%record;
  for (my $ans = 0; $ans < $answers_needed; $ans++) {  }
     $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);  
     $ansnum++;  
  }  
     }  
   
  # Qon 'number' implies each slot gives a digit that indexes the  
  #    the bubbles filled or Qoff or a non number for unbubbled lines.  
         #    and *? for double bubbles on a line.  
  #    these answers are also stored as letters.  
   
  } elsif ($$scantron_config{'Qon'} eq 'number') {  
     if ($currentquest =~ /\?/  
  || $currentquest =~ /\*/  
  || (&occurence_count($currentquest, '\d') > 1)) {  
  push(@{$record{'scantron.doubleerror'}},$questnum);  
  for (my $ans = 0; $ans < $answers_needed; $ans++) {  
     my $bubble = substr($currentquest, $ans, 1);  
     if ($bubble =~ /\d/) {  
  $record{"scantron.$ansnum.answer"} = $alphabet[$bubble];  
     } else {  
  $record{"scantron.$ansnum.answer"}='';  
     }  
     $ansnum++;  
  }  
   
     } elsif (!defined($currentquest)  
      || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest))   
      || (&occurence_count($currentquest, '\d') == 0)) {  
  for (my $ans = 0; $ans < $answers_needed; $ans++ ) {  
     $record{"scantron.$ansnum.answer"}='';  
     $ansnum++;  
   
  }  
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {  
     push(@{$record{"scantron.missingerror"}},$questnum);  
     $ansnum += $answers_needed;  
  }  
   
     } else {  sub scantron_validator_lettnum {
  $currentquest = &digits_to_letters($currentquest);      my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
  for (my $ans =0; $ans < $answers_needed; $ans++) {          $alphabet,$record,$scantron_config,$scan_data) = @_;
     $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);  
     $ansnum++;      # Qon 'letter' implies for each slot in currquest we have:
  }      #    ? or * for doubles, a letter in A-Z for a bubble, and
     }      #    about anything else (esp. a value of Qoff) for missing
  } else {      #    bubbles.
       #
       # Qon 'number' implies each slot gives a digit that indexes the
       #    bubbles filled, or Qoff, or a non-number for unbubbled lines,
       #    and * or ? for double bubbles on a single line.
       #
   
     # Otherwise there's a positional notation;      my $matchon;
     # each bubble line requires Qlength items, and there are filled in      if ($$scantron_config{'Qon'} eq 'letter') {
     # bubbles for each case where there 'Qon' characters.          $matchon = '[A-Z]';
     #      } elsif ($$scantron_config{'Qon'} eq 'number') {
           $matchon = '\d';
       }
       my $occurrences = 0;
       if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
           ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
           ($responsetype_per_response{$questnum-1} eq 'stringresponse') ||
           ($responsetype_per_response{$questnum-1} eq 'imageresponse') ||
           ($responsetype_per_response{$questnum-1} eq 'reactionresponse') ||
           ($responsetype_per_response{$questnum-1} eq 'organicresponse')) {
           my @singlelines = split('',$currquest);
           foreach my $entry (@singlelines) {
               $occurrences = &occurence_count($entry,$matchon);
               if ($occurrences > 1) {
                   last;
               }
           } 
       } else {
           $occurrences = &occurence_count($currquest,$matchon); 
       }
       if (($currquest =~ /\?/ || $currquest =~ /\*/) || ($occurrences > 1)) {
           push(@{$record->{'scantron.doubleerror'}},$quest_id);
           for (my $ans=0; $ans<$answers_needed; $ans++) {
               my $bubble = substr($currquest,$ans,1);
               if ($bubble =~ /$matchon/ ) {
                   if ($$scantron_config{'Qon'} eq 'number') {
                       if ($bubble == 0) {
                           $bubble = 10; 
                       }
                       $record->{"scantron.$ansnum.answer"} = 
                           $alphabet->[$bubble-1];
                   } else {
                       $record->{"scantron.$ansnum.answer"} = $bubble;
                   }
               } else {
                   $record->{"scantron.$ansnum.answer"}='';
               }
               $ansnum++;
           }
       } elsif (!defined($currquest)
               || (&occurence_count($currquest, $$scantron_config{'Qoff'}) == length($currquest))
               || (&occurence_count($currquest,$matchon) == 0)) {
           for (my $ans=0; $ans<$answers_needed; $ans++ ) {
               $record->{"scantron.$ansnum.answer"}='';
               $ansnum++;
           }
           if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
               push(@{$record->{'scantron.missingerror'}},$quest_id);
           }
       } else {
           if ($$scantron_config{'Qon'} eq 'number') {
               $currquest = &digits_to_letters($currquest);            
           }
           for (my $ans=0; $ans<$answers_needed; $ans++) {
               my $bubble = substr($currquest,$ans,1);
               $record->{"scantron.$ansnum.answer"} = $bubble;
               $ansnum++;
           }
       }
       return $ansnum;
   }
   
     my @array=split($$scantron_config{'Qon'},$currentquest,-1);  sub scantron_validator_positional {
       my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
           $whichline,$alphabet,$record,$scantron_config,$scan_data) = @_;
   
     # If the split only  giveas us one element.. the full length of the      # Otherwise there's a positional notation;
     # answser string, no bubbles are filled in:      # each bubble line requires Qlength items, and there are filled in
       # bubbles for each case where there 'Qon' characters.
       #
   
     if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {      my @array=split($$scantron_config{'Qon'},$currquest,-1);
  for (my $ans = 0; $ans < $answers_needed; $ans++ ) {  
     $record{"scantron.$ansnum.answer"}='';  
     $ansnum++;  
   
  }      # If the split only gives us one element.. the full length of the
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {      # answer string, no bubbles are filled in:
     push(@{$record{"scantron.missingerror"}},$questnum);  
  }  
     } elsif (scalar(@array) lt 2) {  
   
  my $location      = length($array[0]);      if ($answers_needed eq '') {
  my $line_num      = $location / $$scantron_config{'Qlength'};          return;
  my $bubble        = $alphabet[$location % $$scantron_config{'Qlength'}];      }
   
  for (my $ans = 0; $ans < $answers_needed; $ans++) {      if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
     if ($ans eq $line_num) {          for (my $ans=0; $ans<$answers_needed; $ans++ ) {
  $record{"scantron.$ansnum.answer"} = $bubble;              $record->{"scantron.$ansnum.answer"}='';
     } else {              $ansnum++;
  $record{"scantron.$ansnum.answer"} = ' ';          }
     }          if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
     $ansnum++;              push(@{$record->{"scantron.missingerror"}},$quest_id);
  }          }
     }      } elsif (scalar(@array) == 2) {
     #  If there's more than one instance of a bubble character          my $location = length($array[0]);
     #  That's a double bubble; with positional notation we can          my $line_num = int($location / $$scantron_config{'Qlength'});
     #  record all the bubbles filled in as well as the           my $bubble   = $alphabet->[$location % $$scantron_config{'Qlength'}];
     #  fact this response consists of multiple bubbles.          for (my $ans=0; $ans<$answers_needed; $ans++) {
     #              if ($ans eq $line_num) {
     else {                  $record->{"scantron.$ansnum.answer"} = $bubble;
  push(@{$record{'scantron.doubleerror'}},$questnum);              } else {
                   $record->{"scantron.$ansnum.answer"} = ' ';
  my $first_answer = $ansnum;              }
  for (my $ans =0; $ans < $answers_needed; $ans++) {              $ansnum++;
     $record{"scantron.$ansnum.answer"} = '';           }
     $ans++;      } else {
  }          #  If there's more than one instance of a bubble character
           #  That's a double bubble; with positional notation we can
  my @ans=@array;          #  record all the bubbles filled in as well as the
  my $i=length($ans[0]);shift(@ans);          #  fact this response consists of multiple bubbles.
  while ($#ans) {          #
     $i+=length($ans[0])+1;          if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
     my $line   = $i/$$scantron_config{'Qlength'} + $first_answer;              ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
     my $bubble = $i%$$scantron_config{'Qlength'};              ($responsetype_per_response{$questnum-1} eq 'stringresponse') ||
               ($responsetype_per_response{$questnum-1} eq 'imageresponse') ||
               ($responsetype_per_response{$questnum-1} eq 'reactionresponse') ||
               ($responsetype_per_response{$questnum-1} eq 'organicresponse')) {
               my $doubleerror = 0;
               while (($currquest >= $$scantron_config{'Qlength'}) && 
                      (!$doubleerror)) {
                  my $currline = substr($currquest,0,$$scantron_config{'Qlength'});
                  $currquest = substr($currquest,$$scantron_config{'Qlength'});
                  my @currarray = split($$scantron_config{'Qon'},$currline,-1);
                  if (length(@currarray) > 2) {
                      $doubleerror = 1;
                  } 
               }
               if ($doubleerror) {
                   push(@{$record->{'scantron.doubleerror'}},$quest_id);
               }
           } else {
               push(@{$record->{'scantron.doubleerror'}},$quest_id);
           }
           my $item = $ansnum;
           for (my $ans=0; $ans<$answers_needed; $ans++) {
               $record->{"scantron.$item.answer"} = '';
               $item ++;
           }
   
     $record{"scantron.$line.answer"}.=$alphabet[$bubble];          my @ans=@array;
     shift(@ans);          my $i=0;
  }          my $increment = 0;
     }          while ($#ans) {
  }              $i+=length($ans[0]) + $increment;
               my $line   = int($i/$$scantron_config{'Qlength'} + $ansnum);
               my $bubble = $i%$$scantron_config{'Qlength'};
               $record->{"scantron.$line.answer"}.=$alphabet->[$bubble];
               shift(@ans);
               $increment = 1;
           }
           $ansnum += $answers_needed;
     }      }
     $record{'scantron.maxquest'}=$questnum;      return $ansnum;
     return \%record;  
 }  }
   
 =pod  =pod
Line 5541  sub scantron_process_corrections { Line 5943  sub scantron_process_corrections {
  &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,   &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
  $which,'answer',   $which,'answer',
  { 'question'=>$question,   { 'question'=>$question,
        'response'=>$env{"form.scantron_correct_Q_$question"}});           'response'=>$env{"form.scantron_correct_Q_$question"},
                                      'questionnum'=>$env{"form.scantron_questionnum_Q_$question"}});
     if ($err) { last; }      if ($err) { last; }
  }   }
     }      }
Line 5646  sub remember_current_skipped { Line 6049  sub remember_current_skipped {
 sub check_for_error {  sub check_for_error {
     my ($r,$result)=@_;      my ($r,$result)=@_;
     if ($result ne 'ok' && $result ne 'not_found' ) {      if ($result ne 'ok' && $result ne 'not_found' ) {
  $r->print("An error occurred ($result) when trying to Remove the existing corrections.");   $r->print(&mt("An error occurred ([_1]) when trying to remove the existing corrections.",$result));
     }      }
 }  }
   
Line 5670  sub scantron_warning_screen { Line 6073  sub scantron_warning_screen {
  $CODElist=$env{'form.scantron_CODElist'};   $CODElist=$env{'form.scantron_CODElist'};
  if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">None</span>'; }   if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">None</span>'; }
  $CODElist=   $CODElist=
     '<tr><td><b>List of CODES to validate against:</b></td><td><tt>'.      '<tr><td><b>'.&mt('List of CODES to validate against:').'</b></td><td><tt>'.
     $env{'form.scantron_CODElist'}.'</tt></td></tr>';      $env{'form.scantron_CODElist'}.'</tt></td></tr>';
     }      }
     return (<<STUFF);      return ('
 <p>  <p>
 <span class="LC_warning">Please double check the information  <span class="LC_warning">
                  below before clicking on '$button_text'</span>  '.&mt('Please double check the information below before clicking on \'[_1]\'',&mt($button_text)).'</span>
 </p>  </p>
 <table>  <table>
 <tr><td><b>Sequence to be Graded:</b></td><td>$title</td></tr>  <tr><td><b>'.&mt('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>  <tr><td><b>'.&mt('Data File that will be used:').'</b></td><td><tt>'.$env{'form.scantron_selectfile'}.'</tt></td></tr>
 $CODElist  '.$CODElist.'
 </table>  </table>
 <br />  <br />
 <p> If this information is correct, please click on '$button_text'.</p>  <p> '.&mt('If this information is correct, please click on \'[_1]\'.',&mt($button_text)).'</p>
 <p> If something is incorrect, please click the 'Grading Menu' button to start over.</p>  <p> '.&mt('If something is incorrect, please click the \'Grading Menu\' button to start over.').'</p>
   
 <br />  <br />
 STUFF  ');
 }  }
   
 =pod  =pod
Line 5709  sub scantron_do_warning { Line 6112  sub scantron_do_warning {
     if ( $env{'form.selectpage'} eq '' ||      if ( $env{'form.selectpage'} eq '' ||
  $env{'form.scantron_selectfile'} eq '' ||   $env{'form.scantron_selectfile'} eq '' ||
  $env{'form.scantron_format'} eq '' ) {   $env{'form.scantron_format'} eq '' ) {
  $r->print("<p>You have forgetten to specify some information. Please go Back and try again.</p>");   $r->print("<p>".&mt('You have forgetten to specify some information. Please go Back and try again.')."</p>");
  if ( $env{'form.selectpage'} eq '') {   if ( $env{'form.selectpage'} eq '') {
     $r->print('<p><span class="LC_error">You have not selected a Sequence to grade</span></p>');      $r->print('<p><span class="LC_error">'.&mt('You have not selected a Sequence to grade').'</span></p>');
  }    } 
  if ( $env{'form.scantron_selectfile'} eq '') {   if ( $env{'form.scantron_selectfile'} eq '') {
     $r->print('<p><span class="LC_error">You have not selected a file that contains the student\'s response data.</span></p>');      $r->print('<p><span class="LC_error">'.&mt('You have not selected a file that contains the student\'s response data.').'</span></p>');
  }    } 
  if ( $env{'form.scantron_format'} eq '') {   if ( $env{'form.scantron_format'} eq '') {
     $r->print('<p><span class="LC_error">You have not selected a the format of the student\'s response data.</span></p>');      $r->print('<p><span class="LC_error">'.&mt('You have not selected a the format of the student\'s response data.').'</span></p>');
  }    } 
     } else {      } else {
  my $warning=&scantron_warning_screen('Grading: Validate Records');   my $warning=&scantron_warning_screen('Grading: Validate Records');
  $r->print(<<STUFF);   $r->print('
 $warning  '.$warning.'
 <input type="submit" name="submit" value="Grading: Validate Records" />  <input type="submit" name="submit" value="'.&mt('Grading: Validate Records').'" />
 <input type="hidden" name="command" value="scantron_validate" />  <input type="hidden" name="command" value="scantron_validate" />
 STUFF  ');
     }      }
     $r->print("</form><br />".&show_grading_menu_form($symb));      $r->print("</form><br />".&show_grading_menu_form($symb));
     return '';      return '';
Line 5760  SCANTRONFORM Line 6163  SCANTRONFORM
    '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";     '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
        $chunk .=         $chunk .=
    '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";     '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
          $chunk .= 
              '<input type="hidden" name="scantron.sub_bubblelines.'.$line.'" value="'.$env{"form.scantron.sub_bubblelines.$line"}.'" />'."\n";
          $chunk .=
              '<input type="hidden" name="scantron.responsetype.'.$line.'" value="'.$env{"form.scantron.responsetype.$line"}.'" />'."\n";
        $result .= $chunk;         $result .= $chunk;
        $line++;         $line++;
    }     }
Line 5804  sub scantron_validate_file { Line 6211  sub scantron_validate_file {
     if ($env{'form.scantron_corrections'}) {      if ($env{'form.scantron_corrections'}) {
  &scantron_process_corrections($r);   &scantron_process_corrections($r);
     }      }
     $r->print("<p>Gathering necessary info.</p>");$r->rflush();      $r->print('<p>'.&mt('Gathering necessary information.').'</p>');$r->rflush();
     #get the student pick code ready      #get the student pick code ready
     $r->print(&Apache::loncommon::studentbrowser_javascript());      $r->print(&Apache::loncommon::studentbrowser_javascript());
     my $max_bubble=&scantron_get_maxbubble();      my $max_bubble=&scantron_get_maxbubble();
Line 5824  sub scantron_validate_file { Line 6231  sub scantron_validate_file {
   
     my $stop=0;      my $stop=0;
     while (!$stop && $currentphase < scalar(@validate_phases)) {      while (!$stop && $currentphase < scalar(@validate_phases)) {
  $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");   $r->print(&mt('Validating '.$validate_phases[$currentphase]).'<br />');
  $r->rflush();   $r->rflush();
  my $which="scantron_validate_".$validate_phases[$currentphase];   my $which="scantron_validate_".$validate_phases[$currentphase];
  {   {
Line 5834  sub scantron_validate_file { Line 6241  sub scantron_validate_file {
     }      }
     if (!$stop) {      if (!$stop) {
  my $warning=&scantron_warning_screen('Start Grading');   my $warning=&scantron_warning_screen('Start Grading');
  $r->print(<<STUFF);   $r->print(&mt('Validation process complete.').'<br />'.
 Validation process complete.<br />                    $warning.
 $warning                    &mt('Perform verification for each student after storage of submissions?').
 <input type="submit" name="submit" value="Start Grading" />                    '&nbsp;<span class="LC_nobreak"><label>'.
 <input type="hidden" name="command" value="scantron_process" />                    '<input type="radio" name="verifyrecord" value="1" />'.&mt('Yes').'</label>'.
 STUFF                    ('&nbsp;'x3).'<label>'.
                     '<input type="radio" name="verifyrecord" value="0" checked="checked" />'.&mt('No').
                     '</label></span><br />'.
                     &mt('Grading will take longer if you use verification.').'<br />'.
                     &mt("Alternatively, the 'Review bubblesheet data' utility (see grading menu) can be used for all students after grading is complete.").'<br /><br />'.
                     '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'.
                     '<input type="hidden" name="command" value="scantron_process" />'."\n");
     } else {      } else {
  $r->print('<input type="hidden" name="command" value="scantron_validate" />');   $r->print('<input type="hidden" name="command" value="scantron_validate" />');
  $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");   $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
     }      }
     if ($stop) {      if ($stop) {
  if ($validate_phases[$currentphase] eq 'sequence') {   if ($validate_phases[$currentphase] eq 'sequence') {
     $r->print('<input type="submit" name="submit" value="Ignore -> " />');      $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' &rarr; " />');
     $r->print(' this error <br />');      $r->print(' '.&mt('this error').' <br />');
   
     $r->print(" <p>Or click the 'Grading Menu' button to start over.</p>");      $r->print(" <p>".&mt("Or click the 'Grading Menu' button to start over.")."</p>");
  } else {   } else {
     $r->print('<input type="submit" name="submit" value="Continue ->" />');              if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
     $r->print(' using corrected info <br />');          $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' &rarr;" onclick="javascript:verify_bubble_radio(this.form)" />');
     $r->print("<input type='submit' value='Skip' name='scantron_skip_record' />");              } else {
     $r->print(" this scanline saving it for later.");                  $r->print('<input type="submit" name="submit" value="'.&mt('Continue').' &rarr;" />');
               }
       $r->print(' '.&mt('using corrected info').' <br />');
       $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />");
       $r->print(" ".&mt("this scanline saving it for later."));
  }   }
     }      }
     $r->print(" </form><br />".&show_grading_menu_form($symb));      $r->print(" </form><br />".&show_grading_menu_form($symb));
Line 5916  sub scantron_remove_scan_data { Line 6332  sub scantron_remove_scan_data {
     }      }
     my $result;      my $result;
     if (@todelete) {      if (@todelete) {
  $result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname);   $result = &Apache::lonnet::del('nohist_scantrondata',
          \@todelete,$cdom,$cname);
       } else {
    $result = 'ok';
     }      }
     return $result;      return $result;
 }  }
Line 6226  sub scantron_validate_sequence { Line 6645  sub scantron_validate_sequence {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
 =pod  
   
 =item scantron_validate_ID  
   
    Validates all scanlines in the selected file to not have any  
    invalid or underspecified student IDs  
   
 =cut  
   
 sub scantron_validate_ID {  sub scantron_validate_ID {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
Line 6299  sub scantron_validate_ID { Line 6711  sub scantron_validate_ID {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
 =pod  
   
 =item scantron_get_correction  
   
    Builds the interface screen to interact with the operator to fix a  
    specific error condition in a specific scanline  
   
  Arguments:  
     $r           - Apache request object  
     $i           - number of the current scanline  
     $scan_record - hash ref as returned from &scantron_parse_scanline()  
     $scan_config - hash ref as returned from &get_scantron_config()  
     $line        - full contents of the current scanline  
     $error       - error condition, valid values are  
                    'incorrectCODE', 'duplicateCODE',  
                    'doublebubble', 'missingbubble',  
                    'duplicateID', 'incorrectID'  
     $arg         - extra information needed  
        For errors:  
          - duplicateID   - paper number that this studentID was seen before on  
          - duplicateCODE - array ref of the paper numbers this CODE was  
                            seen on before  
          - incorrectCODE - current incorrect CODE   
          - doublebubble  - array ref of the bubble lines that have double  
                            bubble errors  
          - missingbubble - array ref of the bubble lines that have missing  
                            bubble errors  
   
 =cut  
   
 sub scantron_get_correction {  sub scantron_get_correction {
     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;      my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
   
 #FIXME in the case of a duplicated ID the previous line, probably need  #FIXME in the case of a duplicated ID the previous line, probably need
 #to show both the current line and the previous one and allow skipping  #to show both the current line and the previous one and allow skipping
 #the previous one or the current one  #the previous one or the current one
   
     $r->print("<p><b>An error was detected ($error)</b>");  
     if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {      if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
  $r->print(" for PaperID <tt>".   $r->print("<p>".&mt("<b>An error was detected ($error)</b>".
   $$scan_record{'scantron.PaperID'}."</tt> \n");      " for PaperID <tt>[_1]</tt>",
       $$scan_record{'scantron.PaperID'})."</p> \n");
     } else {      } else {
  $r->print(" in scanline $i <pre>".   $r->print("<p>".&mt("<b>An error was detected ($error)</b>".
   $line."</pre> \n");      " in scanline [_1] <pre>[_2]</pre>",
     }      $i,$line)."</p> \n");
     my $message="<p>The ID on the form is  <tt>".      }
  $$scan_record{'scantron.ID'}."</tt><br />\n".      my $message="<p>".&mt("The ID on the form is  <tt>[_1]</tt><br />".
  "The name on the paper is ".    "The name on the paper is [_2],[_3]",
  $$scan_record{'scantron.LastName'}.",".    $$scan_record{'scantron.ID'},
  $$scan_record{'scantron.FirstName'}."</p>";    $$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_corrections" value="'.$error.'" />'."\n");
     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");      $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
                              # Array populated for doublebubble or
       my @lines_to_correct;  # missingbubble errors to build javascript
                              # to validate radio button checking   
   
     if ($error =~ /ID$/) {      if ($error =~ /ID$/) {
  if ($error eq 'incorrectID') {   if ($error eq 'incorrectID') {
     $r->print("The encoded ID is not in the classlist</p>\n");      $r->print("<p>".&mt("The encoded ID is not in the classlist").
         "</p>\n");
  } elsif ($error eq 'duplicateID') {   } elsif ($error eq 'duplicateID') {
     $r->print("The encoded ID has also been used by a previous paper $arg</p>\n");      $r->print("<p>".&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."</p>\n");
  }   }
  $r->print($message);   $r->print($message);
  $r->print("<p>How should I handle this? <br /> \n");   $r->print("<p>".&mt("How should I handle this?")." <br /> \n");
  $r->print("\n<ul><li> ");   $r->print("\n<ul><li> ");
  #FIXME it would be nice if this sent back the user ID and   #FIXME it would be nice if this sent back the user ID and
  #could do partial userID matches   #could do partial userID matches
Line 6372  sub scantron_get_correction { Line 6760  sub scantron_get_correction {
  $r->print('</li>');   $r->print('</li>');
     } elsif ($error =~ /CODE$/) {      } elsif ($error =~ /CODE$/) {
  if ($error eq 'incorrectCODE') {   if ($error eq 'incorrectCODE') {
     $r->print("</p><p>The encoded CODE is not in the list of possible CODEs</p>\n");      $r->print("<p>".&mt("The encoded CODE is not in the list of possible CODEs.")."</p>\n");
  } elsif ($error eq 'duplicateCODE') {   } 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>".&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."</p>\n");
  }   }
  $r->print("<p>The CODE on the form is  <tt>'".   $r->print("<p>".&mt("The CODE on the form is  <tt>'[_1]'</tt>",
   $$scan_record{'scantron.CODE'}."'</tt><br />\n");      $$scan_record{'scantron.CODE'})."<br />\n");
  $r->print($message);   $r->print($message);
  $r->print("<p>How should I handle this? <br /> \n");   $r->print("<p>".&mt("How should I handle this?")." <br /> \n");
  $r->print("\n<br /> ");   $r->print("\n<br /> ");
  my $i=0;   my $i=0;
  if ($error eq 'incorrectCODE'    if ($error eq 'incorrectCODE' 
Line 6388  sub scantron_get_correction { Line 6776  sub scantron_get_correction {
     if ($closest > 0) {      if ($closest > 0) {
  foreach my $testcode (@{$closest}) {   foreach my $testcode (@{$closest}) {
     my $checked='';      my $checked='';
     if (!$i) { $checked=' checked="checked" '; }      if (!$i) { $checked=' checked="checked"'; }
     $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.</label><input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");      $r->print("
      <label>
          <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i'$checked />
          ".&mt("Use the similar CODE [_1] instead.",
       "<b><tt>".$testcode."</tt></b>")."
       </label>
       <input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
     $r->print("\n<br />");      $r->print("\n<br />");
     $i++;      $i++;
  }   }
     }      }
  }   }
  if ($$scan_record{'scantron.CODE'}=~/\S/ ) {   if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
     my $checked; if (!$i) { $checked=' checked="checked" '; }      my $checked; if (!$i) { $checked=' checked="checked"'; }
     $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.</label>");      $r->print("
       <label>
           <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked />
          ".&mt("Use the CODE [_1] that is was on the paper, ignoring the error.",
        "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
       </label>");
     $r->print("\n<br />");      $r->print("\n<br />");
  }   }
   
Line 6419  ENDSCRIPT Line 6818  ENDSCRIPT
    "&curCODE=".&escape($$scan_record{'scantron.CODE'}).     "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
    "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});     "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
  if ($env{'form.scantron_CODElist'} =~ /\S/) {    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("
       <label>
          <input type='radio' name='scantron_CODE_resolution' value='use_found' />
          ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
        "<a target='_blank' href='$href'>","</a>")."
       </label> 
       ".&mt("Selected CODE is [_1]",'<input readonly="readonly" type="text" size="8" name="scantron_CODE_selectedvalue" onfocus="javascript:change_radio(\'use_found\')" onchange="javascript:change_radio(\'use_found\')" />'));
     $r->print("\n<br />");      $r->print("\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("
       <label>
          <input type='radio' name='scantron_CODE_resolution' value='use_typed' />
          ".&mt("Use [_1] as the CODE.",
        "</label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" />"));
  $r->print("\n<br /><br />");   $r->print("\n<br /><br />");
     } elsif ($error eq 'doublebubble') {      } elsif ($error eq 'doublebubble') {
  $r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n");   $r->print("<p>".&mt("There have been multiple bubbles scanned for some question(s)")."</p>\n");
   
    # The form field scantron_questions is acutally a list of line numbers.
    # represented by this form so:
   
    my $line_list = &questions_to_line_list($arg);
   
  $r->print('<input type="hidden" name="scantron_questions" value="'.   $r->print('<input type="hidden" name="scantron_questions" value="'.
   join(',',@{$arg}).'" />');    $line_list.'" />');
  $r->print($message);   $r->print($message);
  $r->print("<p>Please indicate which bubble should be used for grading</p>");   $r->print("<p>".&mt("Please indicate which bubble should be used for grading")."</p>");
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my $selected  = &get_response_bubbles($scan_record, $question);      my @linenums = &prompt_for_corrections($r,$question,$scan_config,
     my @select_array = split('',$selected);                                                     $scan_record, $error);
     &scantron_bubble_selector($r,$scan_config,$question,              push(@lines_to_correct,@linenums);
       @select_array);  
  }   }
           $r->print(&verify_bubbles_checked(@lines_to_correct));
     } elsif ($error eq 'missingbubble') {      } elsif ($error eq 'missingbubble') {
  $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");   $r->print("<p>".&mt("There have been <b>no</b> bubbles scanned for some question(s)")."</p>\n");
  $r->print($message);   $r->print($message);
  $r->print("<p>Please indicate which bubble should be used for grading</p>");   $r->print("<p>".&mt("Please indicate which bubble should be used for grading.")."</p>");
  $r->print("Some questions have no scanned bubbles\n");   $r->print(&mt("Some questions have no scanned bubbles.")."\n");
   
    # The form field scantron_questions is actually a list of line numbers not
    # a list of question numbers. Therefore:
    #
   
    my $line_list = &questions_to_line_list($arg);
   
  $r->print('<input type="hidden" name="scantron_questions" value="'.   $r->print('<input type="hidden" name="scantron_questions" value="'.
   join(',',@{$arg}).'" />');    $line_list.'" />');
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my $selected = &get_response_bubbles($scan_record, $question);      my @linenums = &prompt_for_corrections($r,$question,$scan_config,
     &scantron_bubble_selector($r,$scan_config,$question);                                                     $scan_record, $error);
               push(@lines_to_correct,@linenums);
  }   }
           $r->print(&verify_bubbles_checked(@lines_to_correct));
     } else {      } else {
  $r->print("\n<ul>");   $r->print("\n<ul>");
     }      }
     $r->print("\n</li></ul>");      $r->print("\n</li></ul>");
   }
   
   sub verify_bubbles_checked {
       my (@ansnums) = @_;
       my $ansnumstr = join('","',@ansnums);
       my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
       my $output = (<<ENDSCRIPT);
   <script type="text/javascript">
   function verify_bubble_radio(form) {
       var ansnumArray = new Array ("$ansnumstr");
       var need_bubble_count = 0;
       for (var i=0; i<ansnumArray.length; i++) {
           if (form.elements["scantron_correct_Q_"+ansnumArray[i]].length > 1) {
               var bubble_picked = 0; 
               for (var j=0; j<form.elements["scantron_correct_Q_"+ansnumArray[i]].length; j++) {
                   if (form.elements["scantron_correct_Q_"+ansnumArray[i]][j].checked == true) {
                       bubble_picked = 1;
                   }
               }
               if (bubble_picked == 0) {
                   need_bubble_count ++;
               }
           }
       }
       if (need_bubble_count) {
           alert("$warning");
           return;
       }
       form.submit(); 
   }
   </script>
   ENDSCRIPT
       return $output;
   }
   
   =pod
   
   =item  questions_to_line_list
   
   Converts a list of questions into a string of comma separated
   line numbers in the answer sheet used by the questions.  This is
   used to fill in the scantron_questions form field.
   
     Arguments:
        questions    - Reference to an array of questions.
   
   =cut
   
   
   sub questions_to_line_list {
       my ($questions) = @_;
       my @lines;
   
       foreach my $item (@{$questions}) {
           my $question = $item;
           my ($first,$count,$last);
           if ($item =~ /^(\d+)\.(\d+)$/) {
               $question = $1;
               my $subquestion = $2;
               $first = $first_bubble_line{$question-1} + 1;
               my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
               my $subcount = 1;
               while ($subcount<$subquestion) {
                   $first += $subans[$subcount-1];
                   $subcount ++;
               }
               $count = $subans[$subquestion-1];
           } else {
       $first   = $first_bubble_line{$question-1} + 1;
       $count   = $bubble_lines_per_response{$question-1};
           }
           $last = $first+$count-1;
           push(@lines, ($first..$last));
       }
       return join(',', @lines);
   }
   
   =pod 
   
   =item prompt_for_corrections
   
   Prompts for a potentially multiline correction to the
   user's bubbling (factors out common code from scantron_get_correction
   for multi and missing bubble cases).
   
    Arguments:
      $r           - Apache request object.
      $question    - The question number to prompt for.
      $scan_config - The scantron file configuration hash.
      $scan_record - Reference to the hash that has the the parsed scanlines.
      $error       - Type of error
   
    Implicit inputs:
      %bubble_lines_per_response   - Starting line numbers for each question.
                                     Numbered from 0 (but question numbers are from
                                     1.
      %first_bubble_line           - Starting bubble line for each question.
      %subdivided_bubble_lines     - optionresponse, matchresponse and rankresponse 
                                     type problems render as separate sub-questions, 
                                     in exam mode. This hash contains a 
                                     comma-separated list of the lines per 
                                     sub-question.
      %responsetype_per_response   - essayresponse, formularesponse,
                                     stringresponse, imageresponse, reactionresponse,
                                     and organicresponse type problem parts can have
                                     multiple lines per response if the weight
                                     assigned exceeds 10.  In this case, only
                                     one bubble per line is permitted, but more 
                                     than one line might contain bubbles, e.g.
                                     bubbling of: line 1 - J, line 2 - J, 
                                     line 3 - B would assign 22 points.  
   
   =cut
   
   sub prompt_for_corrections {
       my ($r, $question, $scan_config, $scan_record, $error) = @_;
       my ($current_line,$lines);
       my @linenums;
       my $questionnum = $question;
       if ($question =~ /^(\d+)\.(\d+)$/) {
           $question = $1;
           $current_line = $first_bubble_line{$question-1} + 1 ;
           my $subquestion = $2;
           my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
           my $subcount = 1;
           while ($subcount<$subquestion) {
               $current_line += $subans[$subcount-1];
               $subcount ++;
           }
           $lines = $subans[$subquestion-1];
       } else {
           $current_line = $first_bubble_line{$question-1} + 1 ;
           $lines        = $bubble_lines_per_response{$question-1};
       }
       if ($lines > 1) {
           $r->print(&mt('The group of bubble lines below responds to a single question.').'<br />');
           if (($responsetype_per_response{$question-1} eq 'essayresponse') ||
               ($responsetype_per_response{$question-1} eq 'formularesponse') ||
               ($responsetype_per_response{$question-1} eq 'stringresponse') ||
               ($responsetype_per_response{$question-1} eq 'imageresponse') ||
               ($responsetype_per_response{$question-1} eq 'reactionresponse') ||
               ($responsetype_per_response{$question-1} eq 'organicresponse')) {
               $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the exam directed students to leave [quant,_1,line] blank on their bubblesheets.",$lines).'<br /><br />'.&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.').'<br />'.&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.').'<br />'.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'<br /><br />');
           } else {
               $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");
           }
       }
       for (my $i =0; $i < $lines; $i++) {
           my $selected = $$scan_record{"scantron.$current_line.answer"};
    &scantron_bubble_selector($r,$scan_config,$current_line, 
             $questionnum,$error,split('', $selected));
           push(@linenums,$current_line);
    $current_line++;
       }
       if ($lines > 1) {
    $r->print("<hr /><br />");
       }
       return @linenums;
 }  }
   
 =pod  =pod
Line 6464  ENDSCRIPT Line 7045  ENDSCRIPT
  Arguments:   Arguments:
     $r           - Apache request object      $r           - Apache request object
     $scan_config - hash from &get_scantron_config()      $scan_config - hash from &get_scantron_config()
     $quest       - number of the bubble line to make a corrector for      $line        - Number of the line being displayed.
     $selected    - array of letters of previously selected bubbles      $questionnum - Question number (may include subquestion)
       $error       - Type of error.
       @selected    - Array of bubbles picked on this line.
   
 =cut  =cut
   
 sub scantron_bubble_selector {  sub scantron_bubble_selector {
     my ($r,$scan_config,$quest,@selected)=@_;      my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
     my $max=$$scan_config{'Qlength'};      my $max=$$scan_config{'Qlength'};
   
     my $scmode=$$scan_config{'Qon'};      my $scmode=$$scan_config{'Qon'};
   
     my $bubble_length = scalar(@selected);  
   
   
     if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }           if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }     
   
     my $response = $quest-1;  
     my $lines = $bubble_lines_per_response{$response};  
   
     my $total_lines = $lines*2;  
     my @alphabet=('A'..'Z');      my @alphabet=('A'..'Z');
     $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");      $r->print(&Apache::loncommon::start_data_table().
                 &Apache::loncommon::start_data_table_row());
     for (my $l = 0; $l < $lines; $l++) {      $r->print('<td rowspan="2" class="LC_leftcol_header">'.$line.'</td>');
  if ($l != 0) {      for (my $i=0;$i<$max+1;$i++) {
     $r->print('<tr>');   $r->print("\n".'<td align="center">');
  }   if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
    else { $r->print('&nbsp;'); }
  for (my $i=0;$i<$max;$i++) {   $r->print('</td>');
     $r->print("\n".'<td align="center">');      }
     if ($selected[0] eq $alphabet[$i]) {       $r->print(&Apache::loncommon::end_data_table_row().
  $r->print('X');                 &Apache::loncommon::start_data_table_row());
  shift(@selected) ;      for (my $i=0;$i<$max;$i++) {
     } else {    $r->print("\n".
  $r->print('&nbsp;');     '<td><label><input type="radio" name="scantron_correct_Q_'.
     }    $line.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
     $r->print('</td>');      }
           my $nobub_checked = ' ';
  }      if ($error eq 'missingbubble') {
           $nobub_checked = ' checked = "checked" ';
  if ($l == 0) {      }
     my $lspan = $total_lines * 2;   #  2 table rows per bubble line.      $r->print("\n".'<td><label><input type="radio" name="scantron_correct_Q_'.
         $line.'" value="none"'.$nobub_checked.'/>'.&mt('No bubble').
     $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'.                '</label>'."\n".'<input type="hidden" name="scantron_questionnum_Q_'.
       $quest.'" value="none" /> No bubble </label></td>');                $line.'" value="'.$questionnum.'" /></td>');
       $r->print(&Apache::loncommon::end_data_table_row().
  }                &Apache::loncommon::end_data_table());
   
  $r->print('</tr><tr>');  
   
  # FIXME: This may have to be a bit more clever for  
  #        multiline questions (different values e.g..).  
   
  for (my $i=0;$i<$max;$i++) {  
     $r->print("\n".  
       '<td><label><input type="radio" name="scantron_correct_Q_'.  
       $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");  
  }  
  $r->print('</tr>');  
   
       
     }  
     $r->print('</table>');  
 }  }
   
 =pod  =pod
Line 6683  sub scantron_validate_CODE { Line 7242  sub scantron_validate_CODE {
      $line,'duplicateCODE',$usedCODEs{$CODE});       $line,'duplicateCODE',$usedCODEs{$CODE});
     return(1,$currentphase);      return(1,$currentphase);
  }   }
  push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});   push(@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
     }      }
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
Line 6706  sub scantron_validate_doublebubble { Line 7265  sub scantron_validate_doublebubble {
     #get scantron line setup      #get scantron line setup
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
   
     &scantron_get_maxbubble(); # parse needs the bubble line array.      &scantron_get_maxbubble(); # parse needs the bubble line array.
   
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {      for (my $i=0;$i<=$scanlines->{'count'};$i++) {
Line 6723  sub scantron_validate_doublebubble { Line 7281  sub scantron_validate_doublebubble {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
 =pod  
   
 =item scantron_get_maxbubble  
   
    Returns the maximum number of bubble lines that are expected to  sub scantron_get_maxbubble {
    occur. Does this by walking the selected sequence rendering the  
    resource and then checking &Apache::lonxml::get_problem_counter()  
    for what the current value of the problem counter is.  
   
    Caches the results to $env{'form.scantron_maxbubble'},  
    $env{'form.scantron.bubble_lines.n'} and   
    $env{'form.scantron.first_bubble_line.n'}  
    which are the total number of bubble, lines, the number of bubble  
    lines for reponse n and number of the first bubble line for response n.  
   
 =cut  
   
 sub scantron_get_maxbubble {      
     if (defined($env{'form.scantron_maxbubble'}) &&      if (defined($env{'form.scantron_maxbubble'}) &&
  $env{'form.scantron_maxbubble'}) {   $env{'form.scantron_maxbubble'}) {
  &restore_bubble_lines();   &restore_bubble_lines();
Line 6756  sub scantron_get_maxbubble { Line 7298  sub scantron_get_maxbubble {
   
     &Apache::lonxml::clear_problem_counter();      &Apache::lonxml::clear_problem_counter();
   
     my $uname       = $env{'form.student'};      my $uname       = $env{'user.name'};
     my $udom        = $env{'form.userdom'};      my $udom        = $env{'user.domain'};
     my $cid         = $env{'request.course.id'};      my $cid         = $env{'request.course.id'};
     my $total_lines = 0;      my $total_lines = 0;
     %bubble_lines_per_response = ();      %bubble_lines_per_response = ();
     %first_bubble_line         = ();      %first_bubble_line         = ();
       %subdivided_bubble_lines   = ();
       %responsetype_per_response = ();
   
     
     my $response_number = 0;      my $response_number = 0;
     my $bubble_line     = 0;      my $bubble_line     = 0;
     foreach my $resource (@resources) {      foreach my $resource (@resources) {
  my $symb = $resource->symb();          my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom);
  &Apache::lonxml::clear_bubble_lines_for_part();          if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
  my $result=&Apache::lonnet::ssi($resource->src(),      foreach my $part_id (@{$parts}) {
  ('symb' => $resource->symb()),                  my $lines;
  ('grade_target' => 'analyze'),  
  ('grade_courseid' => $cid),          # TODO - make this a persistent hash not an array.
  ('grade_domain' => $udom),  
  ('grade_username' => $uname));                  # optionresponse, matchresponse and rankresponse type items 
  my (undef, $an) =                  # render as separate sub-questions in exam mode.
     split(/_HASH_REF__/,$result, 2);                  if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
                       ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
  my %analysis = &Apache::lonnet::str2hash($an);                      ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
                       my ($numbub,$numshown);
                       if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
                           if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
  foreach my $part_id (@{$analysis{'parts'}}) {                              $numbub = scalar(@{$analysis->{$part_id.'.options'}});
                           }
                       } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
     my $lines = $analysis{"$part_id.bubble_lines"};;                          if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
                               $numbub = scalar(@{$analysis->{$part_id.'.items'}});
     # TODO - make this a persistent hash not an array.                          }
                       } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
                           if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
     $first_bubble_line{$response_number}           = $bubble_line;                              $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
     $bubble_lines_per_response{$response_number}   = $lines;                          }
     $response_number++;                      }
                       if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
                           $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
                       }
                       my $bubbles_per_line = 10;
                       my $inner_bubble_lines = int($numbub/$bubbles_per_line);
                       if (($numbub % $bubbles_per_line) != 0) {
                           $inner_bubble_lines++;
                       }
                       for (my $i=0; $i<$numshown; $i++) {
                           $subdivided_bubble_lines{$response_number} .= 
                               $inner_bubble_lines.',';
                       }
                       $subdivided_bubble_lines{$response_number} =~ s/,$//;
                       $lines = $numshown * $inner_bubble_lines;
                   } else {
                       $lines = $analysis->{"$part_id.bubble_lines"};
                   } 
   
     $bubble_line +=  $lines;                  $first_bubble_line{$response_number} = $bubble_line;
     $total_lines +=  $lines;          $bubble_lines_per_response{$response_number} = $lines;
  }                  $responsetype_per_response{$response_number} = 
                       $analysis->{$part_id.'.type'};
           $response_number++;
   
           $bubble_line +=  $lines;
           $total_lines +=  $lines;
       }
           }
     }      }
     &Apache::lonnet::delenv('scantron\.');      &Apache::lonnet::delenv('scantron.');
   
     &save_bubble_lines();      &save_bubble_lines();
     $env{'form.scantron_maxbubble'} =      $env{'form.scantron_maxbubble'} =
Line 6807  sub scantron_get_maxbubble { Line 7373  sub scantron_get_maxbubble {
     return $env{'form.scantron_maxbubble'};      return $env{'form.scantron_maxbubble'};
 }  }
   
 =pod  
   
 =item scantron_validate_missingbubbles  
   
    Validates all scanlines in the selected file to not have any  
     answers that don't have bubbles that have not been verified  
     to be bubble free.  
   
 =cut  
   
 sub scantron_validate_missingbubbles {  sub scantron_validate_missingbubbles {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
     #get student info      #get student info
Line 6835  sub scantron_validate_missingbubbles { Line 7391  sub scantron_validate_missingbubbles {
  $scan_data);   $scan_data);
  if (!defined($$scan_record{'scantron.missingerror'})) { next; }   if (!defined($$scan_record{'scantron.missingerror'})) { next; }
  my @to_correct;   my @to_correct;
   
    # Probably here's where the error is...
   
  foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {   foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
     if ($missing > $max_bubble) { next; }              my $lastbubble;
               if ($missing =~ /^(\d+)\.(\d+)$/) {
                  my $question = $1;
                  my $subquestion = $2;
                  if (!defined($first_bubble_line{$question -1})) { next; }
                  my $first = $first_bubble_line{$question-1};
                  my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
                  my $subcount = 1;
                  while ($subcount<$subquestion) {
                      $first += $subans[$subcount-1];
                      $subcount ++;
                  }
                  my $count = $subans[$subquestion-1];
                  $lastbubble = $first + $count;
               } else {
                   if (!defined($first_bubble_line{$missing - 1})) { next; }
                   $lastbubble = $first_bubble_line{$missing - 1} + $bubble_lines_per_response{$missing - 1};
               }
               if ($lastbubble > $max_bubble) { next; }
     push(@to_correct,$missing);      push(@to_correct,$missing);
  }   }
  if (@to_correct) {   if (@to_correct) {
Line 6849  sub scantron_validate_missingbubbles { Line 7426  sub scantron_validate_missingbubbles {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
 =pod  
   
 =item scantron_process_students  
   
    Routine that does the actual grading of the bubble sheet information.  
   
    The parsed scanline hash is added to %env   
   
    Then foreach unskipped scanline it does an &Apache::lonnet::ssi()  
    foreach resource , with the form data of  
   
  'submitted'     =>'scantron'   
  'grade_target'  =>'grade',  
  'grade_username'=> username of student  
  'grade_domain'  => domain of student  
  'grade_courseid'=> of course  
  'grade_symb'    => symb of resource to grade  
   
     This triggers a grading pass. The problem grading code takes care  
     of converting the bubbled letter information (now in %env) into a  
     valid submission.  
   
 =cut  
   
 sub scantron_process_students {  sub scantron_process_students {
     my ($r) = @_;      my ($r) = @_;
   
     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});      my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
     my ($symb)=&get_symb($r);      my ($symb)=&get_symb($r);
     if (!$symb) {return '';}      if (!$symb) {
    return '';
       }
     my $default_form_data=&defaultFormData($symb);      my $default_form_data=&defaultFormData($symb);
   
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
Line 6887  sub scantron_process_students { Line 7444  sub scantron_process_students {
     my $navmap=Apache::lonnavmaps::navmap->new();      my $navmap=Apache::lonnavmaps::navmap->new();
     my $map=$navmap->getResourceByUrl($sequence);      my $map=$navmap->getResourceByUrl($sequence);
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);      my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 #    $r->print("geto ".scalar(@resources)."<br />");      my (%grader_partids_by_symb,%grader_randomlists_by_symb);
       &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                               \%grader_randomlists_by_symb);
       foreach my $resource (@resources) {
           my $ressymb = $resource->symb();
           my ($analysis,$parts) =
               &scantron_partids_tograde($resource,$env{'request.course.id'},
                                         $env{'user.name'},$env{'user.domain'},1);
           $grader_partids_by_symb{$ressymb} = $parts;
           if (ref($analysis) eq 'HASH') {
               if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
                   $grader_randomlists_by_symb{$ressymb} = 
                       $analysis->{'parts_withrandomlist'};
               }
           }
       }
   
       my ($uname,$udom);
     my $result= <<SCANTRONFORM;      my $result= <<SCANTRONFORM;
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
   <input type="hidden" name="command" value="scantron_configphase" />    <input type="hidden" name="command" value="scantron_configphase" />
Line 6896  SCANTRONFORM Line 7470  SCANTRONFORM
     $r->print($result);      $r->print($result);
   
     my @delayqueue;      my @delayqueue;
     my %completedstudents;      my (%completedstudents,%scandata);
           
       my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));
     my $count=&get_todo_count($scanlines,$scan_data);      my $count=&get_todo_count($scanlines,$scan_data);
     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',      my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',
      'Scantron Progress',$count,       'Scantron Progress',$count,
     'inline',undef,'scantronupload');      'inline',undef,'scantronupload');
     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,      &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
   'Processing first student');    'Processing first student');
       $r->print('<br />');
     my $start=&Time::HiRes::time();      my $start=&Time::HiRes::time();
     my $i=-1;      my $i=-1;
     my ($uname,$udom,$started);      my $started;
   
     &scantron_get_maxbubble(); # Need the bubble lines array to parse.      &scantron_get_maxbubble(); # Need the bubble lines array to parse.
   
       # If an ssi failed in scantron_get_maxbubble, put an error message out to
       # the user and return.
   
       if ($ssi_error) {
    $r->print("</form>");
    &ssi_print_error($r);
    $r->print(&show_grading_menu_form($symb));
           &Apache::lonnet::remove_lock($lock);
    return ''; # Dunno why the other returns return '' rather than just returning.
       }
   
       my %lettdig = &letter_to_digits();
       my $numletts = scalar(keys(%lettdig));
   
     while ($i<$scanlines->{'count'}) {      while ($i<$scanlines->{'count'}) {
   ($uname,$udom)=('','');    ($uname,$udom)=('','');
   $i++;    $i++;
Line 6935  SCANTRONFORM Line 7525  SCANTRONFORM
   }    }
   ($uname,$udom)=split(/:/,$uname);    ($uname,$udom)=split(/:/,$uname);
   
           my %partids_by_symb;
           foreach my $resource (@resources) {
               my $ressymb = $resource->symb();
               if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
                   my ($analysis,$parts) =
                       &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom);
                   $partids_by_symb{$ressymb} = $parts;
               } else {
                   $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
               }
           }
   
  &Apache::lonxml::clear_problem_counter();   &Apache::lonxml::clear_problem_counter();
   &Apache::lonnet::appenv(%$scan_record);    &Apache::lonnet::appenv($scan_record);
   
  if (&scantron_clear_skip($scanlines,$scan_data,$i)) {   if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
     &scantron_putfile($scanlines,$scan_data);      &scantron_putfile($scanlines,$scan_data);
  }   }
   
  my $i=0;          my $scancode;
  foreach my $resource (@resources) {          if ((exists($scan_record->{'scantron.CODE'})) &&
     $i++;              (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
     my %form=('submitted'     =>'scantron',              $scancode = $scan_record->{'scantron.CODE'};
       'grade_target'  =>'grade',          } else {
       'grade_username'=>$uname,              $scancode = '';
       'grade_domain'  =>$udom,          }
       'grade_courseid'=>$env{'request.course.id'},  
       'grade_symb'    =>$resource->symb());          if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
     if (exists($scan_record->{'scantron.CODE'})                                     \@resources,\%partids_by_symb) eq 'ssi_error') {
  &&               $ssi_error = 0; # So end of handler error message does not trigger.
  &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {              $r->print("</form>");
  $form{'CODE'}=$scan_record->{'scantron.CODE'};              &ssi_print_error($r);
     } else {              $r->print(&show_grading_menu_form($symb));
  $form{'CODE'}='';              &Apache::lonnet::remove_lock($lock);
     }              return '';      # Why return ''?  Beats me.
     my $result=&Apache::lonnet::ssi($resource->src(),%form);          }
     if ($result ne '') {  
     }  
     if (&Apache::loncommon::connection_aborted($r)) { last; }  
  }  
  $completedstudents{$uname}={'line'=>$line};   $completedstudents{$uname}={'line'=>$line};
  if (&Apache::loncommon::connection_aborted($r)) { last; }          if ($env{'form.verifyrecord'}) {
               my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
               my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
               chomp($studentdata);
               $studentdata =~ s/\r$//;
               my $studentrecord = '';
               my $counter = -1;
               foreach my $resource (@resources) {
                   my $ressymb = $resource->symb();
                   ($counter,my $recording) =
                       &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
                                                $counter,$studentdata,$partids_by_symb{$ressymb},
                                                \%scantron_config,\%lettdig,$numletts);
                   $studentrecord .= $recording;
               }
               if ($studentrecord ne $studentdata) {
                   &Apache::lonxml::clear_problem_counter();
                   if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
                                              \@resources,\%partids_by_symb) eq 'ssi_error') {
                       $ssi_error = 0; # So end of handler error message does not trigger.
                       $r->print("</form>");
                       &ssi_print_error($r);
                       $r->print(&show_grading_menu_form($symb));
                       &Apache::lonnet::remove_lock($lock);
                       delete($completedstudents{$uname});
                       return '';
                   }
                   $counter = -1;
                   $studentrecord = '';
                   foreach my $resource (@resources) {
                       my $ressymb = $resource->symb();
                       ($counter,my $recording) =
                           &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
                                                    $counter,$studentdata,$partids_by_symb{$ressymb},
                                                    \%scantron_config,\%lettdig,$numletts);
                       $studentrecord .= $recording;
                   }
                   if ($studentrecord ne $studentdata) {
                       $r->print('<p><span class="LC_error">');
                       if ($scancode eq '') {
                           $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2].',
                                     $uname.':'.$udom,$scan_record->{'scantron.ID'}));
                       } else {
                           $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2] and CODE: [_3].',
                                     $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
                       }
                       $r->print('</span><br />'.&Apache::loncommon::start_data_table()."\n".
                                 &Apache::loncommon::start_data_table_header_row()."\n".
                                 '<th>'.&mt('Source').'</th><th>'.&mt('Bubbled responses').'</th>'.
                                 &Apache::loncommon::end_data_table_header_row()."\n".
                                 &Apache::loncommon::start_data_table_row().
                                 '<td>'.&mt('Bubble Sheet').'</td>'.
                                 '<td><span class="LC_nobreak">'.$studentdata.'</span></td>'.
                                 &Apache::loncommon::end_data_table_row().
                                 &Apache::loncommon::start_data_table_row().
                                 '<td>Stored submissions</td>'.
                                 '<td><span class="LC_nobreak">'.$studentrecord.'</span></td>'."\n".
                                 &Apache::loncommon::end_data_table_row().
                                 &Apache::loncommon::end_data_table().'</p>');
                   } else {
                       $r->print('<br /><span class="LC_warning">'.
                                &mt('A second grading pass was needed for user: [_1] with ID: [_2], because a mismatch was seen on the first pass.',$uname.':'.$udom,$scan_record->{'scantron.ID'}).'<br />'.
                                &mt("As a consequence, this user's submission history records two tries.").
                                    '</span><br />');
                   }
               }
           }
           if (&Apache::loncommon::connection_aborted($r)) { last; }
     } continue {      } continue {
  &Apache::lonxml::clear_problem_counter();   &Apache::lonxml::clear_problem_counter();
  &Apache::lonnet::delenv('scantron\.');   &Apache::lonnet::delenv('scantron.');
     }      }
     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);      &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       &Apache::lonnet::remove_lock($lock);
 #    my $lasttime = &Time::HiRes::time()-$start;  #    my $lasttime = &Time::HiRes::time()-$start;
 #    $r->print("<p>took $lasttime</p>");  #    $r->print("<p>took $lasttime</p>");
   
Line 6978  SCANTRONFORM Line 7646  SCANTRONFORM
     return '';      return '';
 }  }
   
 =pod  sub graders_resources_pass {
       my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb) = @_;
 =item scantron_upload_scantron_data      if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) && 
           (ref($grader_randomlists_by_symb) eq 'HASH')) {
     Creates the screen for adding a new bubble sheet data file to a course.          foreach my $resource (@{$resources}) {
               my $ressymb = $resource->symb();
               my ($analysis,$parts) =
                   &scantron_partids_tograde($resource,$env{'request.course.id'},
                                             $env{'user.name'},$env{'user.domain'},1);
               $grader_partids_by_symb->{$ressymb} = $parts;
               if (ref($analysis) eq 'HASH') {
                   if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
                       $grader_randomlists_by_symb->{$ressymb} =
                           $analysis->{'parts_withrandomlist'};
                   }
               }
           }
       }
       return;
   }
   
 =cut  sub grade_student_bubbles {
       my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts) = @_;
       if (ref($resources) eq 'ARRAY') {
           my $count = 0;
           foreach my $resource (@{$resources}) {
               my $ressymb = $resource->symb();
               my %form = ('submitted'      => 'scantron',
                           'grade_target'   => 'grade',
                           'grade_username' => $uname,
                           'grade_domain'   => $udom,
                           'grade_courseid' => $env{'request.course.id'},
                           'grade_symb'     => $ressymb,
                           'CODE'           => $scancode
                          );
               if (ref($parts) eq 'HASH') {
                   if (ref($parts->{$ressymb}) eq 'ARRAY') {
                       foreach my $part (@{$parts->{$ressymb}}) {
                           $form{'scantron_questnum_start.'.$part} =
                               1+$env{'form.scantron.first_bubble_line.'.$count};
                           $count++;
                       }
                   }
               }
               my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form);
               return 'ssi_error' if ($ssi_error);
               last if (&Apache::loncommon::connection_aborted($r));
           }
       }
       return;
   }
   
 sub scantron_upload_scantron_data {  sub scantron_upload_scantron_data {
     my ($r)=@_;      my ($r)=@_;
     $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));      my $dom = $env{'request.role.domain'};
       my $domdesc = &Apache::lonnet::domain($dom,'description');
       $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',      my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
   'domainid',    'domainid',
   'coursename');    'coursename',$dom);
     my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},      my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'.
    'domainid');                         ('&nbsp'x2).&mt('(shows course personnel)'); 
     my $default_form_data=&defaultFormData(&get_symb($r,1));      my $default_form_data=&defaultFormData(&get_symb($r,1));
     $r->print(<<UPLOAD);      $r->print('
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
     function checkUpload(formname) {      function checkUpload(formname) {
  if (formname.upfile.value == "") {   if (formname.upfile.value == "") {
     alert("Please use the browse button to select a file from your local directory.");      alert("'.&mt('Please use the browse button to select a file from your local directory.').'");
     return false;      return false;
  }   }
           if (formname.courseid.value == "") {
               alert("'.&mt('Please use the \"Select Course\" link to open a separate window where you can search for a course to which a file can be uploaded.').'");
               return false;
           }
  formname.submit();   formname.submit();
     }      }
   
       function ToSyllabus() {
           var cdom = '."'$dom'".';
           var cnum = document.rules.courseid.value;
           if (cdom == "" || cdom == null) {
               return;
           }
           if (cnum == "" || cnum == null) {
              return;
           }
           syllwin=window.open("/public/"+cdom+"/"+cnum+"/syllabus","LONCAPASyllabus",
                               "height=350,width=350,scrollbars=yes,menubar=no");
           return;
       }
   
 </script>  </script>
   
 <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>  <h3>'.&mt('Send scanned bubblesheet data to a course').'</h3>
 $default_form_data  
 <table>  <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
 <tr><td>$select_link </td></tr>  '.$default_form_data.
 <tr><td>Course ID:   </td><td><input name='courseid' type='text' />  </td></tr>    &Apache::lonhtmlcommon::start_pick_box().
 <tr><td>Course Name: </td><td><input name='coursename' type='text' /></td></tr>    &Apache::lonhtmlcommon::row_title(&mt('Course ID')).
 <tr><td>Domain:      </td><td>$domsel                                </td></tr>    '<input name="courseid" type="text" size="30" />'.$select_link.
 <tr><td>File to upload:</td><td><input type="file" name="upfile" size="50" /></td></tr>    &Apache::lonhtmlcommon::row_closure().
 </table>    &Apache::lonhtmlcommon::row_title(&mt('Course Name')).
 <input name='command' value='scantronupload_save' type='hidden' />    '<input name="coursename" type="text" size="30" />'.$syllabuslink.
 <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />    &Apache::lonhtmlcommon::row_closure().
     &Apache::lonhtmlcommon::row_title(&mt('Domain')).
     '<input name="domainid" type="hidden" />'.$domdesc.
     &Apache::lonhtmlcommon::row_closure().
     &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
     '<input type="file" name="upfile" size="50" />'.
     &Apache::lonhtmlcommon::row_closure(1).
     &Apache::lonhtmlcommon::end_pick_box().'<br />
   
   <input name="command" value="scantronupload_save" type="hidden" />
   <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Scantron Data').'" />
 </form>  </form>
 UPLOAD  ');
     return '';      return '';
 }  }
   
 =pod  
   
 =item scantron_upload_scantron_data_save  
   
    Adds a provided bubble information data file to the course if user  
    has the correct privileges to do so.    
   
 =cut  
   
 sub scantron_upload_scantron_data_save {  sub scantron_upload_scantron_data_save {
     my($r)=@_;      my($r)=@_;
Line 7037  sub scantron_upload_scantron_data_save { Line 7772  sub scantron_upload_scantron_data_save {
     my $doanotherupload=      my $doanotherupload=
  '<br /><form action="/adm/grades" method="post">'."\n".   '<br /><form action="/adm/grades" method="post">'."\n".
  '<input type="hidden" name="command" value="scantronupload" />'."\n".   '<input type="hidden" name="command" value="scantronupload" />'."\n".
  '<input type="submit" name="submit" value="Do Another Upload" />'."\n".   '<input type="submit" name="submit" value="'.&mt('Do Another Upload').'" />'."\n".
  '</form>'."\n";   '</form>'."\n";
     if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&      if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
  !&Apache::lonnet::allowed('usc',   !&Apache::lonnet::allowed('usc',
     $env{'form.domainid'}.'_'.$env{'form.courseid'})) {      $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
  $r->print("You are not allowed to upload Scantron data to the requested course.<br />");   $r->print(&mt("You are not allowed to upload Scantron data to the requested course.")."<br />");
  if ($symb) {   if ($symb) {
     $r->print(&show_grading_menu_form($symb));      $r->print(&show_grading_menu_form($symb));
  } else {   } else {
Line 7051  sub scantron_upload_scantron_data_save { Line 7786  sub scantron_upload_scantron_data_save {
  return '';   return '';
     }      }
     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});      my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
     $r->print("Doing upload to ".$coursedata{'description'}." <br />");      my $uploadedfile;
     my $fname=$env{'form.upfile.filename'};      $r->print('<h3>'.&mt("Uploading file to [_1]",$coursedata{'description'}).'</h3>');
     #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) {      if (length($env{'form.upfile'}) < 2) {
  $r->print("<span class=\"LC_error\">Error:</span> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.");          $r->print(&mt('[_1]Error:[_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.','<span class="LC_error">','</span>','<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
     } else {      } else {
  my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);          my $result = 
  if ($result =~ m|^/uploaded/|) {              &Apache::lonnet::userfileupload('upfile','','scantron','','','',
     $r->print("<span class=\"LC_success\">Success:</span> Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");                                              $env{'form.courseid'},$env{'form.domainid'});
    if ($result =~ m{^/uploaded/}) {
       $r->print(&mt('[_1]Success:[_2] Successfully uploaded [_3] bytes of data into location: [_4]',
                             '<span class="LC_success">','</span>',(length($env{'form.upfile'})-1),
     '<span class="LC_filename">'.$result.'</span>'));
               ($uploadedfile) = ($result =~ m{/([^/]+)$});
               $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
                                                          $env{'form.courseid'},$uploadedfile));
  } else {   } else {
     $r->print("<span class=\"LC_error\">Error:</span> An error (".$result.") occurred when attempting to upload the file, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>");      $r->print(&mt('[_1]Error:[_2] An error ([_3]) occurred when attempting to upload the file, [_4]',
                             '<span class="LC_error">','</span>',$result,
     '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
  }   }
     }      }
     if ($symb) {      if ($symb) {
Line 7086  sub scantron_upload_scantron_data_save { Line 7815  sub scantron_upload_scantron_data_save {
     return '';      return '';
 }  }
   
 =pod  sub validate_uploaded_scantron_file {
       my ($cdom,$cname,$fname) = @_;
 =item valid_file      my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname);
       my @lines;
    Validates that the requested bubble data file exists in the course.      if ($scanlines ne '-1') {
           @lines=split("\n",$scanlines,-1);
 =cut      }
       my $output;
       if (@lines) {
           my (%counts,$max_match_format);
           my ($max_match_count,$max_match_pct) = (0,0);
           my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
           my %idmap = &username_to_idmap($classlist);
           foreach my $key (keys(%idmap)) {
               my $lckey = lc($key);
               $idmap{$lckey} = $idmap{$key};
           }
           my %unique_formats;
           my @formatlines = &get_scantronformat_file();
           foreach my $line (@formatlines) {
               chomp($line);
               my @config = split(/:/,$line);
               my $idstart = $config[5];
               my $idlength = $config[6];
               if (($idstart ne '') && ($idlength > 0)) {
                   if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') {
                       push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]); 
                   } else {
                       $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]];
                   }
               }
           }
           foreach my $key (keys(%unique_formats)) {
               my ($idstart,$idlength) = split(':',$key);
               %{$counts{$key}} = (
                                  'found'   => 0,
                                  'total'   => 0,
                                 );
               foreach my $line (@lines) {
                   next if ($line =~ /^#/);
                   next if ($line =~ /^[\s\cz]*$/);
                   my $id = substr($line,$idstart-1,$idlength);
                   $id = lc($id);
                   if (exists($idmap{$id})) {
                       $counts{$key}{'found'} ++;
                   }
                   $counts{$key}{'total'} ++;
               }
               if ($counts{$key}{'total'}) {
                   my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'});
                   if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
                       $max_match_pct = $percent_match;
                       $max_match_format = $key;
                       $max_match_count = $counts{$key}{'total'};
                   }
               }
           }
           if (ref($unique_formats{$max_match_format}) eq 'ARRAY') {
               my $format_descs;
               my $numwithformat = @{$unique_formats{$max_match_format}};
               for (my $i=0; $i<$numwithformat; $i++) {
                   my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]);
                   if ($i<$numwithformat-2) {
                       $format_descs .= '"<i>'.$desc.'</i>", ';
                   } elsif ($i==$numwithformat-2) {
                       $format_descs .= '"<i>'.$desc.'</i>" '.&mt('and').' ';
                   } elsif ($i==$numwithformat-1) {
                       $format_descs .= '"<i>'.$desc.'</i>"';
                   }
               }
               my $showpct = sprintf("%.0f",$max_match_pct).'%';
               $output .= '<br />'.&mt('Comparison of student IDs in the uploaded file with the course roster found matches for [_1] of the [_2] entries in the file (for the format defined for [_3]).','<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
                          '<br />'.&mt('A low percentage of matches results from one of the following:').'<ul>'.
                          '<li>'.&mt('The file was uploaded to the wrong course').'</li>'.
                          '<li>'.&mt('The data are not in the format expected for the domain: [_1]',
                                     '<i>'.$cdom.'</i>').'</li>'.
                          '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
                          '<li>'.&mt('The course roster is not up to date').'</li>'.
                          '</ul>';
           }
       } else {
           $output = '<span class="LC_warning">'.&mt('Uploaded file contained no data').'</span>';
       }
       return $output;
   }
   
 sub valid_file {  sub valid_file {
     my ($requested_file)=@_;      my ($requested_file)=@_;
Line 7102  sub valid_file { Line 7909  sub valid_file {
     return 0;      return 0;
 }  }
   
 =pod  
   
 =item scantron_download_scantron_data  
   
    Shows a list of the three internal files (original, corrected,  
    skipped) for a specific bubble sheet data file that exists in the  
    course.  
   
 =cut  
   
 sub scantron_download_scantron_data {  sub scantron_download_scantron_data {
     my ($r)=@_;      my ($r)=@_;
     my $default_form_data=&defaultFormData(&get_symb($r,1));      my $default_form_data=&defaultFormData(&get_symb($r,1));
Line 7119  sub scantron_download_scantron_data { Line 7916  sub scantron_download_scantron_data {
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $file=$env{'form.scantron_selectfile'};      my $file=$env{'form.scantron_selectfile'};
     if (! &valid_file($file)) {      if (! &valid_file($file)) {
  $r->print(<<ERROR);   $r->print('
  <p>   <p>
     The requested file name was invalid.      '.&mt('The requested file name was invalid.').'
         </p>          </p>
 ERROR  ');
  $r->print(&show_grading_menu_form(&get_symb($r,1)));   $r->print(&show_grading_menu_form(&get_symb($r,1)));
  return;   return;
     }      }
Line 7133  ERROR Line 7930  ERROR
     &Apache::lonnet::allowuploaded('/adm/grades',$orig);      &Apache::lonnet::allowuploaded('/adm/grades',$orig);
     &Apache::lonnet::allowuploaded('/adm/grades',$corrected);      &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
     &Apache::lonnet::allowuploaded('/adm/grades',$skipped);      &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
     $r->print(<<DOWNLOAD);      $r->print('
     <p>      <p>
  <a href="$orig">Original</a> file as uploaded by the scantron office.   '.&mt('[_1]Original[_2] file as uploaded by the scantron office.',
         '<a href="'.$orig.'">','</a>').'
     </p>      </p>
     <p>      <p>
  <a href="$corrected">Corrections</a>, a file of corrected records that were used in grading.   '.&mt('[_1]Corrections[_2], a file of corrected records that were used in grading.',
         '<a href="'.$corrected.'">','</a>').'
     </p>      </p>
     <p>      <p>
  <a href="$skipped">Skipped</a>, a file of records that were skipped.   '.&mt('[_1]Skipped[_2], a file of records that were skipped.',
         '<a href="'.$skipped.'">','</a>').'
     </p>      </p>
 DOWNLOAD  ');
     $r->print(&show_grading_menu_form(&get_symb($r,1)));      $r->print(&show_grading_menu_form(&get_symb($r,1)));
     return '';      return '';
 }  }
   
 =pod  sub checkscantron_results {
       my ($r) = @_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $grading_menu_button=&show_grading_menu_form($symb);
       my $cid = $env{'request.course.id'};
       my %lettdig = &letter_to_digits();
       my $numletts = scalar(keys(%lettdig));
       my $cnum = $env{'course.'.$cid.'.num'};
       my $cdom = $env{'course.'.$cid.'.domain'};
       my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
       my %record;
       my %scantron_config =
           &Apache::grades::get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&Apache::grades::username_to_idmap($classlist);
       my $navmap=Apache::lonnavmaps::navmap->new();
       my $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
       my (%grader_partids_by_symb,%grader_randomlists_by_symb);
       &graders_resources_pass(\@resources,\%grader_partids_by_symb,                             \%grader_randomlists_by_symb);
   
 =back      my ($uname,$udom);
       my (%scandata,%lastname,%bylast);
       $r->print('
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");
   
       my @delayqueue;
       my %completedstudents;
   
       my $count=&Apache::grades::get_todo_count($scanlines,$scan_data);
       my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron/Submissions Comparison Status',
                                       'Progress of Scantron Data/Submission Records Comparison',$count,
                                       'inline',undef,'checkscantron');
       my ($username,$domain,$started);
   
       &scantron_get_maxbubble();  # Need the bubble lines array to parse.
   
       &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
                                             'Processing first student');
       my $start=&Time::HiRes::time();
       my $i=-1;
   
       while ($i<$scanlines->{'count'}) {
           ($username,$domain,$uname)=('','','');
           $i++;
           my $line=&Apache::grades::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=
               &Apache::grades::scantron_parse_scanline($line,$i,\%scantron_config,
                                                        $scan_data);
           unless ($uname=&Apache::grades::scantron_find_student($scan_record,$scan_data,
                                                                 \%idmap,$i)) {
               &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                                   'Unable to find a student that matches',1);
               next;
           }
           if (exists $completedstudents{$uname}) {
               &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                                   'Student '.$uname.' has multiple sheets',2);
               next;
           }
           my $pid = $scan_record->{'scantron.ID'};
           $lastname{$pid} = $scan_record->{'scantron.LastName'};
           push(@{$bylast{$lastname{$pid}}},$pid);
           my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
           $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
           chomp($scandata{$pid});
           $scandata{$pid} =~ s/\r$//;
           ($username,$domain)=split(/:/,$uname);
           my $counter = -1;
           foreach my $resource (@resources) {
               my $parts;
               my $ressymb = $resource->symb();
               if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
                   (my $analysis,$parts) =
                       &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain);
               } else {
                   $parts = $grader_partids_by_symb{$ressymb};
               }
               ($counter,my $recording) =
                   &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
                                            $scandata{$pid},$parts,
                                            \%scantron_config,\%lettdig,$numletts);
               $record{$pid} .= $recording;
           }
       }
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       $r->print('<br />');
       my ($okstudents,$badstudents,$numstudents,$passed,$failed);
       $passed = 0;
       $failed = 0;
       $numstudents = 0;
       foreach my $last (sort(keys(%bylast))) {
           if (ref($bylast{$last}) eq 'ARRAY') {
               foreach my $pid (sort(@{$bylast{$last}})) {
                   my $showscandata = $scandata{$pid};
                   my $showrecord = $record{$pid};
                   $showscandata =~ s/\s/&nbsp;/g;
                   $showrecord =~ s/\s/&nbsp;/g;
                   if ($scandata{$pid} eq $record{$pid}) {
                       my $css_class = ($passed % 2)?'LC_odd_row':'LC_even_row';
                       $okstudents .= '<tr class="'.$css_class.'">'.
   '<td>'.&mt('Scantron').'</td><td>'.$showscandata.'</td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
   '</tr>'."\n".
   '<tr class="'.$css_class.'">'."\n".
   '<td>Submissions</td><td>'.$showrecord.'</td></tr>'."\n";
                       $passed ++;
                   } else {
                       my $css_class = ($failed % 2)?'LC_odd_row':'LC_even_row';
                       $badstudents .= '<tr class="'.$css_class.'"><td>'.&mt('Scantron').'</td><td><span class="LC_nobreak">'.$scandata{$pid}.'</span></td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
   '</tr>'."\n".
   '<tr class="'.$css_class.'">'."\n".
   '<td>Submissions</td><td><span class="LC_nobreak">'.$record{$pid}.'</span></td>'."\n".
   '</tr>'."\n";
                       $failed ++;
                   }
                   $numstudents ++;
               }
           }
       }
       $r->print('<p>'.&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for <b>[quant,_1,student]</b>  ([_2] scantron lines/student).',$numstudents,$env{'form.scantron_maxbubble'}).'</p>');
       $r->print('<p>'.&mt('Exact matches for <b>[quant,_1,student]</b>.',$passed).'<br />'.&mt('Discrepancies detected for <b>[quant,_1,student]</b>.',$failed).'</p>');
       if ($passed) {
           $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').'<br /><br />');
           $r->print(&Apache::loncommon::start_data_table()."\n".
                    &Apache::loncommon::start_data_table_header_row()."\n".
                    '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                    &Apache::loncommon::end_data_table_header_row()."\n".
                    $okstudents."\n".
                    &Apache::loncommon::end_data_table().'<br />');
       }
       if ($failed) {
           $r->print(&mt('Students with differences between bubblesheet data and submissions are as follows:').'<br /><br />');
           $r->print(&Apache::loncommon::start_data_table()."\n".
                    &Apache::loncommon::start_data_table_header_row()."\n".
                    '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                    &Apache::loncommon::end_data_table_header_row()."\n".
                    $badstudents."\n".
                    &Apache::loncommon::end_data_table()).'<br />'.
                    &mt('Differences can occur if submissions were modified using manual grading after a bubblesheet grading pass.').'<br />'.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original bubblesheets.');  
       }
       $r->print('</form><br />'.$grading_menu_button);
       return;
   }
   
   sub verify_scantron_grading {
       my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
           $scantron_config,$lettdig,$numletts) = @_;
       my ($record,%expected,%startpos);
       return ($counter,$record) if (!ref($resource));
       return ($counter,$record) if (!$resource->is_problem());
       my $symb = $resource->symb();
       return ($counter,$record) if (ref($partids) ne 'ARRAY');
       foreach my $part_id (@{$partids}) {
           $counter ++;
           $expected{$part_id} = 0;
           if ($env{"form.scantron.sub_bubblelines.$counter"}) {
               my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$counter"});
               foreach my $item (@sub_lines) {
                   $expected{$part_id} += $item;
               }
           } else {
               $expected{$part_id} = $env{"form.scantron.bubblelines.$counter"};
           }
           $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
       }
       if ($symb) {
           my %recorded;
           my (%returnhash) = &Apache::lonnet::restore($symb,$cid,$domain,$username);
           if ($returnhash{'version'}) {
               my %lasthash=();
               my $version;
               for ($version=1;$version<=$returnhash{'version'};$version++) {
                   foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
                       $lasthash{$key}=$returnhash{$version.':'.$key};
                   }
               }
               foreach my $key (keys(%lasthash)) {
                   if ($key =~ /\.scantron$/) {
                       my $value = &unescape($lasthash{$key});
                       my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/);
                       if ($value eq '') {
                           for (my $i=0; $i<$expected{$part_id}; $i++) {
                               for (my $j=0; $j<$scantron_config->{'length'}; $j++) {
                                   $recorded{$part_id} .= $scantron_config->{'Qoff'};
                               }
                           }
                       } else {
                           my @tocheck;
                           my @items = split(//,$value);
                           if (($scantron_config->{'Qon'} eq 'letter') ||
                               ($scantron_config->{'Qon'} eq 'number')) {
                               if (@items < $expected{$part_id}) {
                                   my $fragment = substr($scandata,$startpos{$part_id},$expected{$part_id});
                                   my @singles = split(//,$fragment);
                                   foreach my $pos (@singles) {
                                       if ($pos eq ' ') {
                                           push(@tocheck,$pos);
                                       } else {
                                           my $next = shift(@items);
                                           push(@tocheck,$next);
                                       }
                                   }
                               } else {
                                   @tocheck = @items;
                               }
                               foreach my $letter (@tocheck) {
                                   if ($scantron_config->{'Qon'} eq 'letter') {
                                       if ($letter !~ /^[A-J]$/) {
                                           $letter = $scantron_config->{'Qoff'};
                                       }
                                       $recorded{$part_id} .= $letter;
                                   } elsif ($scantron_config->{'Qon'} eq 'number') {
                                       my $digit;
                                       if ($letter !~ /^[A-J]$/) {
                                           $digit = $scantron_config->{'Qoff'};
                                       } else {
                                           $digit = $lettdig->{$letter};
                                       }
                                       $recorded{$part_id} .= $digit;
                                   }
                               }
                           } else {
                               @tocheck = @items;
                               for (my $i=0; $i<$expected{$part_id}; $i++) {
                                   my $curr_sub = shift(@tocheck);
                                   my $digit;
                                   if ($curr_sub =~ /^[A-J]$/) {
                                       $digit = $lettdig->{$curr_sub}-1;
                                   }
                                   if ($curr_sub eq 'J') {
                                       $digit += scalar($numletts);
                                   }
                                   for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                                       if ($j == $digit) {
                                           $recorded{$part_id} .= $scantron_config->{'Qon'};
                                       } else {
                                           $recorded{$part_id} .= $scantron_config->{'Qoff'};
                                       }
                                   }
                               }
                           }
                       }
                   }
               }
           }
           foreach my $part_id (@{$partids}) {
               if ($recorded{$part_id} eq '') {
                   for (my $i=0; $i<$expected{$part_id}; $i++) {
                       for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                           $recorded{$part_id} .= $scantron_config->{'Qoff'};
                       }
                   }
               }
               $record .= $recorded{$part_id};
           }
       }
       return ($counter,$record);
   }
   
   sub letter_to_digits { 
       my %lettdig = (
                       A => 1,
                       B => 2,
                       C => 3,
                       D => 4,
                       E => 5,
                       F => 6,
                       G => 7,
                       H => 8,
                       I => 9,
                       J => 0,
                     );
       return %lettdig;
   }
   
 =cut  
   
 #-------- end of section for handling grading scantron forms -------  #-------- end of section for handling grading scantron forms -------
 #  #
Line 7167  sub show_grading_menu_form { Line 8246  sub show_grading_menu_form {
  '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".   '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".   '<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
  '<input type="hidden" name="command" value="gradingmenu" />'."\n".   '<input type="hidden" name="command" value="gradingmenu" />'."\n".
  '<input type="submit" name="submit" value="Grading Menu" />'."\n".   '<input type="submit" name="submit" value="'.&mt('Grading Menu').'" />'."\n".
  '</form>'."\n";   '</form>'."\n";
     return $result;      return $result;
 }  }
Line 7191  sub grading_menu { Line 8270  sub grading_menu {
     my $probTitle = &Apache::lonnet::gettitle($symb);      my $probTitle = &Apache::lonnet::gettitle($symb);
     my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);      my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
   
     #  
     # Define menu data  
     $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);  
     my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});  
     $request->print($table);      $request->print($table);
     my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),      my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
                   'handgrade'=>$hdgrade,                    'handgrade'=>$hdgrade,
Line 7203  sub grading_menu { Line 8278  sub grading_menu {
                   'saveState'=>"",                    'saveState'=>"",
                   'gradingMenu'=>1,                    'gradingMenu'=>1,
                   'showgrading'=>"yes");                    'showgrading'=>"yes");
     my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      
     my @menu = ({ url => $url,      my $url1 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                      name => &mt('Manual Grading/View Submissions'),      
                      short_description =>   
     &mt('Start the process of hand grading submissions.'),  
                  });  
     $fields{'command'} = 'csvform';      $fields{'command'} = 'csvform';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,      
                    name => &mt('Upload Scores'),  
                    short_description =>   
             &mt('Specify a file containing the class scores for current resource.')});  
     $fields{'command'} = 'processclicker';      $fields{'command'} = 'processclicker';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      my $url3 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,      
                    name => &mt('Process Clicker'),  
                    short_description =>   
             &mt('Specify a file containing the clicker information for this resource.')});  
     $fields{'command'} = 'scantron_selectphase';      $fields{'command'} = 'scantron_selectphase';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      my $url4 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,      
                    name => &mt('Grade/Manage Scantron Forms'),      my @menu = ({ categorytitle=>'Course Grading',
                    short_description =>               items =>[
             &mt('')});                          { linktext => 'Manual Grading/View Submissions',
     $fields{'command'} = 'verify';                      url => $url1,
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);                      permission => 'F',
     push (@menu, { url => "",                      icon => 'edit-find-replace.png',
                    name => &mt('Verify Receipt'),                      linktitle => 'Start the process of hand grading submissions.'
                    short_description =>                           },
             &mt('')});                     { linktext => 'Upload Scores',
                       url => $url2,
                       permission => 'F',
                       icon => 'uploadscores.png',
                       linktitle => 'Specify a file containing the class scores for current resource.'
                      },
                      { linktext => 'Process Clicker',
                       url => $url3,
                       permission => 'F',
                       icon => 'addClickerInfoFile.png',
                       linktitle => 'Specify a file containing the clicker information for this resource.'
                      },
                      { linktext => 'Grade/Manage/Review Scantron Forms',
                       url => $url4,
                       permission => 'F',
                       icon => 'stat.png',
                       linktitle => 'Grade scantron exams, upload/download scantron data files, and review previously graded scantron exams.'
                      }
                       ]
               });
   
       #$fields{'command'} = 'verify';
       #$url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     #      #
     # Create the menu      # Create the menu
     my $Str;      my $Str;
Line 7241  sub grading_menu { Line 8329  sub grading_menu {
     $Str .= '<input type="hidden" name="command" value="" />'.      $Str .= '<input type="hidden" name="command" value="" />'.
     '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".      '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".   '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
  '<input type="hidden" name="probTitle"   value="'.$probTitle.'" ue="" />'."\n".   '<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
  '<input type="hidden" name="saveState"   value="" />'."\n".   '<input type="hidden" name="saveState"   value="" />'."\n".
  '<input type="hidden" name="gradingMenu" value="1" />'."\n".   '<input type="hidden" name="gradingMenu" value="1" />'."\n".
  '<input type="hidden" name="showgrading" value="yes" />'."\n";   '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
     foreach my $menudata (@menu) {      $Str .= Apache::lonhtmlcommon::generate_menu(@menu);
         if ($menudata->{'name'} ne &mt('Verify Receipt')) {      #$menudata->{'jscript'}
             $Str .='    <h3><a '.      $Str .='<hr /><input type="button" value="'.&mt('Verify Receipt').'" '.
                 $menudata->{'jscript'}.          ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
                 ' href="'.          ' /> '.
                 $menudata->{'url'}.'" >'.          &Apache::lonnet::recprefix($env{'request.course.id'}).
                 $menudata->{'name'}."</a></h3>\n";          '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />';
         } else {  
             $Str .='    <h3><input type="button" value="Verify Receipt" '.  
                 $menudata->{'jscript'}.  
                 ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.  
                 ' /></h3>';  
             $Str .= ('&nbsp;'x8).  
                     ' receipt: '.&Apache::lonnet::recprefix($env{'request.course.id'}).  
                     '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />';  
         }  
         $Str .= '    '.('&nbsp;'x8).$menudata->{'short_description'}.  
             "\n";  
     }  
     $Str .="</dl>\n";  
     $Str .="</form>\n";      $Str .="</form>\n";
       my $receiptalert = &mt("Please enter a receipt number given by a student in the receipt box.");
     $request->print(<<GRADINGMENUJS);      $request->print(<<GRADINGMENUJS);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
     function checkChoice(formname,val,cmdx) {      function checkChoice(formname,val,cmdx) {
Line 7294  sub grading_menu { Line 8371  sub grading_menu {
  if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}   if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
  if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}   if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
  if (checkOpt) {   if (checkOpt) {
     alert("Please enter a receipt number given by a student in the receipt box.");      alert("$receiptalert");
     formname.receipt.value = "";      formname.receipt.value = "";
     formname.receipt.focus();      formname.receipt.focus();
     return false;      return false;
Line 7315  sub submit_options { Line 8392  sub submit_options {
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $probTitle = &Apache::lonnet::gettitle($symb);      my $probTitle = &Apache::lonnet::gettitle($symb);
   
       my $receiptalert = &mt("Please enter a receipt number given by a student in the receipt box."); 
     $request->print(<<GRADINGMENUJS);      $request->print(<<GRADINGMENUJS);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
     function checkChoice(formname,val,cmdx) {      function checkChoice(formname,val,cmdx) {
Line 7342  sub submit_options { Line 8420  sub submit_options {
  if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}   if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
  if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}   if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
  if (checkOpt) {   if (checkOpt) {
     alert("Please enter a receipt number given by a student in the receipt box.");      alert("$receiptalert");
     formname.receipt.value = "";      formname.receipt.value = "";
     formname.receipt.focus();      formname.receipt.focus();
     return false;      return false;
Line 7352  sub submit_options { Line 8430  sub submit_options {
 </script>  </script>
 GRADINGMENUJS  GRADINGMENUJS
     &commonJSfunctions($request);      &commonJSfunctions($request);
     my $result='<h3>&nbsp;<span class="LC_info">Manual Grading/View Submission</span></h3>';  
     my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);      my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
     $result.=$table;      my $result;
     my (undef,$sections) = &getclasslist('all','0');      my (undef,$sections) = &getclasslist('all','0');
     my $savedState = &savedState();      my $savedState = &savedState();
     my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});      my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});
Line 7362  GRADINGMENUJS Line 8439  GRADINGMENUJS
     my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});      my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});
     my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});      my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
   
       # Preselect sections
       my $selsec="";
       if (ref($sections)) {
           foreach my $section (sort(@$sections)) {
               $selsec.='<option value="'.$section.'" '.
                   ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";
           }
       }
   
     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".      $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
  '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".   '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".   '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
Line 7371  GRADINGMENUJS Line 8457  GRADINGMENUJS
  '<input type="hidden" name="gradingMenu" value="1" />'."\n".   '<input type="hidden" name="gradingMenu" value="1" />'."\n".
  '<input type="hidden" name="showgrading" value="yes" />'."\n";   '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
     $result.='<table border="0"><tr><td bgcolor=#777777>'."\n".      $result.='
  '<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n".  <h2>
  '&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".    '.&mt('Grade Current Resource').'
  '<tr bgcolor="#ffffe6" valign="top"><td>'."\n";  </h2>
   <div>
     $result.='<table width="100%" border="0">';    '.$table.'
     $result.='<tr bgcolor="#ffffe6" valign="top">'."\n";  </div>
     $result.='<td><b>'.&mt('Sections').'</b></td>';  
     $result.='<td><b>'.&mt('Groups').'</b></td>';  <div class="LC_columnSection">
     $result.='<td><b>'.&mt('Access Status').'</td>'."\n";    
     $result.='<td><b>'.&mt('Submission Status').'</td>'."\n";      <fieldset>
     $result.='</tr>';        <legend>
     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".         '.&mt('Sections').'
  '&nbsp;<select name="section" multiple="multiple" size="3">'."\n";        </legend>
     if (ref($sections)) {        <select name="section" multiple="multiple" size="5">'."\n";
  foreach (sort (@$sections)) {      $result.= $selsec;
     $result.='<option value="'.$_.'" '.  
  ($saveSec eq $_ ? 'selected="selected"':'').'>'.$_.'</option>'."\n";  
  }  
     }  
     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';      $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
     $result.= '</td><td>'."\n";      $result.='
     $result.= &Apache::lonstatistics::GroupSelect('group','multiple',3);      </fieldset>
     $result.='</td><td>'."\n";    
     $result.=&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,3,undef,'mult');      <fieldset>
         <legend>
     $result.='</td>';          '.&mt('Groups').'
     $result.='<td><select name="submitonly" size="3">'.        </legend>
  '<option value="yes" '.        '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
  ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>'.      </fieldset>
  '<option value="queued" '.    
  ($saveSub eq 'queued' ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>'.      <fieldset>
  '<option value="graded" '.        <legend>
  ($saveSub eq 'graded' ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>'.          '.&mt('Access Status').'
  '<option value="incorrect" '.        </legend>
  ($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>'.        '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'
  '<option value="all" '.      </fieldset>
  ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>';    
       <fieldset>
     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3"><label>'.        <legend>
  '<input type="radio" name="radioChoice" value="submission" '.          '.&mt('Submission Status').'
  ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').        </legend>
  '</label> </td></tr>'."\n";        <select name="submitonly" size="5">
            <option value="yes" '.      ($saveSub eq 'yes'       ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>
     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3">'.           <option value="queued" '.   ($saveSub eq 'queued'    ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>
  '<label><input type="radio" name="radioChoice" value="viewgrades" '.           <option value="graded" '.   ($saveSub eq 'graded'    ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>
  ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.           <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>
  '<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n";                   <option value="all" '.      ($saveSub eq 'all'       ? 'selected="selected"' : '').'>'.&mt('with any status').'</option>
         </select>
     $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'.      </fieldset>
  '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.    
  '</td></tr>'."\n";  </div>
   
   <br />
     $result.='<tr bgcolor="#ffffe6" valign="top"><td colspan="3">'.            <div>
  '<br /><label><input type="radio" name="radioChoice" value="pickStudentPage" '.              <div>
  ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.                <label>
  'The <b>complete</b> set/page/sequence/folder: For one student</label></td></tr>'."\n";                  <input type="radio" name="radioChoice" value="submission" '.
                     ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.
     $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'.               &mt('Select individual students to grade and view submissions.').'
  '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.        </label> 
  '</td></tr></table>'."\n";              </div>
               <div>
     $result.='</td>'; #<td valign="top">';        <label>
                   <input type="radio" name="radioChoice" value="viewgrades" '.
 #    $result.='<table width="100%" border="0">';                    ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
 #    $result.='<tr bgcolor="#ffffe6"><td>'.                      &mt('Grade all selected students in a grading table.').'
 # '<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.                </label>
 # ' '.&mt('scores from file').' </td></tr>'."\n";              </div>
 #              <div>
 #    $result.='<tr bgcolor="#ffffe6"><td>'.        <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next').' &rarr;" />
 #        '<input type="button" onClick="javascript:checkChoice(this.form,\'6\',\'processclicker\');" value="'.&mt('Process').'" />'.              </div>
 #        ' '.&mt('clicker file').' </td></tr>'."\n";            </div>
 #  
 #    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.  
 # '<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'.          <h2>
 # '" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n";           '.&mt('Grade Complete Folder for One Student').'
 #          </h2>
 #    if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) {          <div>
 # $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.              <div>
 #    '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'.                <label>
 #    ' '.&mt('receipt').': '.                  <input type="radio" name="radioChoice" value="pickStudentPage" '.
 #    &Apache::lonnet::recprefix($env{'request.course.id'}).    ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
 #    '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'.    &mt('The <b>complete</b> page/sequence/folder: For one student').'
 #    '</td></tr>'."\n";                </label>
 #    }               </div>
 #    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.              <div>
 # '<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'.        <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next').' &rarr;" />
 # '" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n";              </div>
 #    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.          </div>
 # '<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'.    </form>';
 # '" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n";      $result .= &show_grading_menu_form($symb);
 #  
 #    $result.='</table>'."\n".'</td>';  
     $result.= '</tr></table>'."\n".  
  '</td></tr></table></form>'."\n";  
     return $result;      return $result;
 }  }
   
Line 7563  sub process_clicker { Line 8641  sub process_clicker {
     $result.=$table;      $result.=$table;
     $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";      $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
     $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";      $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
     $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource').      $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource.').
         '.</b></td></tr>'."\n";          '</b></td></tr>'."\n";
     $result.='<tr bgcolor=#ffffe6><td>'."\n";      $result.='<tr bgcolor=#ffffe6><td>'."\n";
 # Attempt to restore parameters from last session, set defaults if not present  # Attempt to restore parameters from last session, set defaults if not present
     my %Saveable_Parameters=&clicker_grading_parameters();      my %Saveable_Parameters=&clicker_grading_parameters();
Line 7576  sub process_clicker { Line 8654  sub process_clicker {
     if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }      if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
   
     my %checked;      my %checked;
     foreach my $gradingmechanism ('attendance','personnel','specific') {      foreach my $gradingmechanism ('attendance','personnel','specific','given') {
        if ($env{'form.gradingmechanism'} eq $gradingmechanism) {         if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
           $checked{$gradingmechanism}="checked='checked'";            $checked{$gradingmechanism}=' checked="checked"';
        }         }
     }      }
   
Line 7587  sub process_clicker { Line 8665  sub process_clicker {
     my $attendance=&mt("Award points just for participation");      my $attendance=&mt("Award points just for participation");
     my $personnel=&mt("Correctness determined from response by course personnel");      my $personnel=&mt("Correctness determined from response by course personnel");
     my $specific=&mt("Correctness determined from response with clicker ID(s)");       my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
       my $given=&mt("Correctness determined from given list of answers").' '.
                 '<font size="-2"><tt>('.&mt("Provide comma-separated list. Use '*' for any answer correct, '-' for skip").')</tt></font>';
     my $pcorrect=&mt("Percentage points for correct solution");      my $pcorrect=&mt("Percentage points for correct solution");
     my $pincorrect=&mt("Percentage points for incorrect solution");      my $pincorrect=&mt("Percentage points for incorrect solution");
     my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',      my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
Line 7640  function sanitycheck() { Line 8720  function sanitycheck() {
 <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />  <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 <input type="file" name="upfile" size="50" />  <input type="file" name="upfile" size="50" />
 <br /><label>$type: $selectform</label>  <br /><label>$type: $selectform</label>
 <br /><label><input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" />$attendance </label>  <br /><label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onClick="sanitycheck()" />$attendance </label>
 <br /><label><input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" />$personnel</label>  <br /><label><input type="radio" name="gradingmechanism" value="personnel"$checked{'personnel'} onClick="sanitycheck()" />$personnel</label>
 <br /><label><input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" />$specific </label>  <br /><label><input type="radio" name="gradingmechanism" value="specific"$checked{'specific'} onClick="sanitycheck()" />$specific </label>
 <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />  <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
   <br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onClick="sanitycheck()" />$given </label>
   <br />&nbsp;&nbsp;&nbsp;
   <input type="text" name="givenanswer" size="50" />
 <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />  <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
 <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>  <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>
 <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>  <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>
Line 7670  sub process_clicker_file { Line 8753  sub process_clicker_file {
  $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';   $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
  return $result.&show_grading_menu_form($symb);   return $result.&show_grading_menu_form($symb);
     }      }
       if (($env{'form.gradingmechanism'} eq 'given') && ($env{'form.givenanswer'}!~/\S/)) {
           $result.='<span class="LC_error">'.&mt('You need to specify the correct answer').'</span>';
           return $result.&show_grading_menu_form($symb);
       }
       my $foundgiven=0;
       if ($env{'form.gradingmechanism'} eq 'given') {
           $env{'form.givenanswer'}=~s/^\s*//gs;
           $env{'form.givenanswer'}=~s/\s*$//gs;
           $env{'form.givenanswer'}=~s/[^a-zA-Z0-9\.\*\-]+/\,/g;
           $env{'form.givenanswer'}=uc($env{'form.givenanswer'});
           my @answers=split(/\,/,$env{'form.givenanswer'});
           $foundgiven=$#answers+1;
       }
     my %clicker_ids=&gather_clicker_ids();      my %clicker_ids=&gather_clicker_ids();
     my %correct_ids;      my %correct_ids;
     if ($env{'form.gradingmechanism'} eq 'personnel') {      if ($env{'form.gradingmechanism'} eq 'personnel') {
Line 7688  sub process_clicker_file { Line 8784  sub process_clicker_file {
     }      }
     if ($env{'form.gradingmechanism'} eq 'attendance') {      if ($env{'form.gradingmechanism'} eq 'attendance') {
  $result.=&mt('Score based on attendance only');   $result.=&mt('Score based on attendance only');
       } elsif ($env{'form.gradingmechanism'} eq 'given') {
           $result.=&mt('Score based on [_1] ([_2] answers)','<tt>'.$env{'form.givenanswer'}.'</tt>',$foundgiven);
     } else {      } else {
  my $number=0;   my $number=0;
  $result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';   $result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
Line 7733  sub process_clicker_file { Line 8831  sub process_clicker_file {
 <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />  <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
 <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />  <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
 ENDHEADER  ENDHEADER
       if ($env{'form.gradingmechanism'} eq 'given') {
          $result.='<input type="hidden" name="correct:given" value="'.$env{'form.givenanswer'}.'" />';
       } 
     my %responses;      my %responses;
     my @questiontitles;      my @questiontitles;
     my $errormsg='';      my $errormsg='';
Line 7745  ENDHEADER Line 8846  ENDHEADER
     }      }
     $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.      $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
              '<input type="hidden" name="number" value="'.$number.'" />'.               '<input type="hidden" name="number" value="'.$number.'" />'.
              &mt('Awarding [_1] percent for corrion(s)',$number).'<br />'.  
              '<input type="hidden" name="number" value="'.$number.'" />'.  
              &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',               &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
                  $env{'form.pcorrect'},$env{'form.pincorrect'}).                   $env{'form.pcorrect'},$env{'form.pincorrect'}).
              '<br />';               '<br />';
       if (($env{'form.gradingmechanism'} eq 'given') && ($number!=$foundgiven)) {
          $result.='<span class="LC_error">'.&mt('Number of given answers does not agree with number of questions in file.').'</span>';
          return $result.&show_grading_menu_form($symb);
       } 
 # Remember Question Titles  # Remember Question Titles
 # FIXME: Possibly need delimiter other than ":"  # FIXME: Possibly need delimiter other than ":"
     for (my $i=0;$i<$number;$i++) {      for (my $i=0;$i<$number;$i++) {
Line 7793  ENDHEADER Line 8896  ENDHEADER
     }      }
     $result.='<hr />'.      $result.='<hr />'.
              &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);               &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
     if ($env{'form.gradingmechanism'} ne 'attendance') {      if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
        if ($correct_count==0) {         if ($correct_count==0) {
           $errormsg.="Found no correct answers answers for grading!";            $errormsg.="Found no correct answers answers for grading!";
        } elsif ($correct_count>1) {         } elsif ($correct_count>1) {
Line 7864  sub interwrite_eval { Line 8967  sub interwrite_eval {
         $id=~s/[\-\:]//g;          $id=~s/[\-\:]//g;
         $idresponses{$id}[$number]=$entries[6];          $idresponses{$id}[$number]=$entries[6];
     }      }
     foreach my $id (keys %idresponses) {      foreach my $id (keys(%idresponses)) {
        $$responses{$id}=join(',',@{$idresponses{$id}});         $$responses{$id}=join(',',@{$idresponses{$id}});
        $$responses{$id}=~s/^\s*\,//;         $$responses{$id}=~s/^\s*\,//;
     }      }
Line 7938  ENDHEADER Line 9041  ENDHEADER
        if ($user) {          if ($user) { 
           my @answer=split(/\,/,$env{$key});            my @answer=split(/\,/,$env{$key});
           my $sum=0;            my $sum=0;
             my $realnumber=$number;
           for (my $i=0;$i<$number;$i++) {            for (my $i=0;$i<$number;$i++) {
              if ($answer[$i]) {               if ($answer[$i]) {
                 if ($gradingmechanism eq 'attendance') {                  if ($gradingmechanism eq 'attendance') {
                    $sum+=$pcorrect;                     $sum+=$pcorrect;
                   } elsif ($answer[$i] eq '*') {
                      $sum+=$pcorrect;
                   } elsif ($answer[$i] eq '-') {
                      $realnumber--;
                 } else {                  } else {
                    if ($answer[$i] eq $correct[$i]) {                     if ($answer[$i] eq $correct[$i]) {
                       $sum+=$pcorrect;                        $sum+=$pcorrect;
Line 7951  ENDHEADER Line 9059  ENDHEADER
                 }                  }
              }               }
           }            }
           my $ave=$sum/(100*$number);            my $ave=$sum/(100*$realnumber);
 # Store  # Store
           my ($username,$domain)=split(/\:/,$user);            my ($username,$domain)=split(/\:/,$user);
           my %grades=();            my %grades=();
Line 7969  ENDHEADER Line 9077  ENDHEADER
        }         }
     }      }
 # We are done  # We are done
     $result.='<br />'.&mt('Successfully stored grades for [_1] student(s).',$storecount).      $result.='<br />'.&mt('Successfully stored grades for [quant,_1,student].',$storecount).
              '</td></tr></table>'."\n".               '</td></tr></table>'."\n".
              '</td></tr></table><br /><br />'."\n";               '</td></tr></table><br /><br />'."\n";
     return $result.&show_grading_menu_form($symb);      return $result.&show_grading_menu_form($symb);
Line 7994  sub handler { Line 9102  sub handler {
  &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));   &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
     }      }
   
       $ssi_error = 0;
     $request->print(&Apache::loncommon::start_page('Grading'));      my $brcrum = [{href=>"/adm/grades",text=>"Grading"}];
       $request->print(&Apache::loncommon::start_page('Grading',undef,
                                             {'bread_crumbs' => $brcrum}));
     if ($symb eq '' && $command eq '') {      if ($symb eq '' && $command eq '') {
  if ($env{'user.adv'}) {   if ($env{'user.adv'}) {
     if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&      if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&
Line 8007  sub handler { Line 9117  sub handler {
  if ($tsymb) {   if ($tsymb) {
     my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);      my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
     if (&Apache::lonnet::allowed('mgr',$tcrsid)) {      if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
  $request->print(&Apache::lonnet::ssi_body('/res/'.$url,   $request->print(&ssi_with_retries('/res/'.$url, $ssi_retries,
   ('grade_username' => $tuname,    ('grade_username' => $tuname,
    'grade_domain' => $tudom,     'grade_domain' => $tudom,
    'grade_courseid' => $tcrsid,     'grade_courseid' => $tcrsid,
Line 8090  sub handler { Line 9200  sub handler {
   } elsif ($command eq 'scantron_download' &&    } elsif ($command eq 'scantron_download' &&
  &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {   &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
      $request->print(&scantron_download_scantron_data($request));       $request->print(&scantron_download_scantron_data($request));
           } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) {
               $request->print(&checkscantron_results($request));     
  } elsif ($command) {   } elsif ($command) {
     $request->print("Access Denied ($command)");      $request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
  }   }
     }      }
       if ($ssi_error) {
    &ssi_print_error($request);
       }
     $request->print(&Apache::loncommon::end_page());      $request->print(&Apache::loncommon::end_page());
     &reset_caches();      &reset_caches();
     return '';      return '';
Line 8102  sub handler { Line 9217  sub handler {
 1;  1;
   
 __END__;  __END__;
   
   
   =head1 NAME
   
   Apache::grades
   
   =head1 SYNOPSIS
   
   Handles the viewing of grades.
   
   This is part of the LearningOnline Network with CAPA project
   described at http://www.lon-capa.org.
   
   =head1 OVERVIEW
   
   Do an ssi with retries:
   While I'd love to factor out this with the vesrion in lonprintout,
   that would either require a data coupling between modules, which I refuse to perpetuate (there's quite enough of that already), or would require the invention of another infrastructure
   I'm not quite ready to invent (e.g. an ssi_with_retry object).
   
   At least the logic that drives this has been pulled out into loncommon.
   
   
   
   ssi_with_retries - Does the server side include of a resource.
                        if the ssi call returns an error we'll retry it up to
                        the number of times requested by the caller.
                        If we still have a proble, no text is appended to the
                        output and we set some global variables.
                        to indicate to the caller an SSI error occurred.  
                        All of this is supposed to deal with the issues described
                        in LonCAPA BZ 5631 see:
                        http://bugs.lon-capa.org/show_bug.cgi?id=5631
                        by informing the user that this happened.
   
   Parameters:
     resource   - The resource to include.  This is passed directly, without
                  interpretation to lonnet::ssi.
     form       - The form hash parameters that guide the interpretation of the resource
                  
     retries    - Number of retries allowed before giving up completely.
   Returns:
     On success, returns the rendered resource identified by the resource parameter.
   Side Effects:
     The following global variables can be set:
      ssi_error                - If an unrecoverable error occurred this becomes true.
                                 It is up to the caller to initialize this to false
                                 if desired.
      ssi_error_resource  - If an unrecoverable error occurred, this is the value
                                 of the resource that could not be rendered by the ssi
                                 call.
      ssi_error_message   - The error string fetched from the ssi response
                                 in the event of an error.
   
   
   =head1 HANDLER SUBROUTINE
   
   ssi_with_retries()
   
   =head1 SUBROUTINES
   
   =over
   
   =item scantron_get_correction() : 
   
      Builds the interface screen to interact with the operator to fix a
      specific error condition in a specific scanline
   
    Arguments:
       $r           - Apache request object
       $i           - number of the current scanline
       $scan_record - hash ref as returned from &scantron_parse_scanline()
       $scan_config - hash ref as returned from &get_scantron_config()
       $line        - full contents of the current scanline
       $error       - error condition, valid values are
                      'incorrectCODE', 'duplicateCODE',
                      'doublebubble', 'missingbubble',
                      'duplicateID', 'incorrectID'
       $arg         - extra information needed
          For errors:
            - duplicateID   - paper number that this studentID was seen before on
            - duplicateCODE - array ref of the paper numbers this CODE was
                              seen on before
            - incorrectCODE - current incorrect CODE 
            - doublebubble  - array ref of the bubble lines that have double
                              bubble errors
            - missingbubble - array ref of the bubble lines that have missing
                              bubble errors
   
   =item  scantron_get_maxbubble() : 
   
      Returns the maximum number of bubble lines that are expected to
      occur. Does this by walking the selected sequence rendering the
      resource and then checking &Apache::lonxml::get_problem_counter()
      for what the current value of the problem counter is.
   
      Caches the results to $env{'form.scantron_maxbubble'},
      $env{'form.scantron.bubble_lines.n'}, 
      $env{'form.scantron.first_bubble_line.n'} and
      $env{"form.scantron.sub_bubblelines.n"}
      which are the total number of bubble, lines, the number of bubble
      lines for response n and number of the first bubble line for response n,
      and a comma separated list of numbers of bubble lines for sub-questions
      (for optionresponse, matchresponse, and rankresponse items), for response n.  
   
   
   =item  scantron_validate_missingbubbles() : 
   
      Validates all scanlines in the selected file to not have any
       answers that don't have bubbles that have not been verified
       to be bubble free.
   
   =item  scantron_process_students() : 
   
      Routine that does the actual grading of the bubble sheet information.
   
      The parsed scanline hash is added to %env 
   
      Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
      foreach resource , with the form data of
   
    'submitted'     =>'scantron' 
    'grade_target'  =>'grade',
    'grade_username'=> username of student
    'grade_domain'  => domain of student
    'grade_courseid'=> of course
    'grade_symb'    => symb of resource to grade
   
       This triggers a grading pass. The problem grading code takes care
       of converting the bubbled letter information (now in %env) into a
       valid submission.
   
   =item  scantron_upload_scantron_data() :
   
       Creates the screen for adding a new bubble sheet data file to a course.
   
   =item  scantron_upload_scantron_data_save() : 
   
      Adds a provided bubble information data file to the course if user
      has the correct privileges to do so. 
   
   =item  valid_file() :
   
      Validates that the requested bubble data file exists in the course.
   
   =item  scantron_download_scantron_data() : 
   
      Shows a list of the three internal files (original, corrected,
      skipped) for a specific bubble sheet data file that exists in the
      course.
   
   =item  scantron_validate_ID() : 
   
      Validates all scanlines in the selected file to not have any
      invalid or underspecified student/employee IDs
   
   =back
   
   =cut

Removed from v.1.460  
changed lines
  Added in v.1.574


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