Diff for /loncom/homework/grades.pm between versions 1.514 and 1.572

version 1.514, 2008/03/12 02:46:52 version 1.572, 2009/05/07 19:36:45
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 58  my $ssi_error_resource; Line 60  my $ssi_error_resource;
 my $ssi_error_message;  my $ssi_error_message;
   
   
 #  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 occured.    
 #                      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 occured this becomes true.  
 #                               It is up to the caller to initialize this to false  
 #                               if desired.  
 #    ssi_last_error_resource  - If an unrecoverable error occured, this is the value  
 #                               of the resource that could not be rendered by the ssi  
 #                               call.  
 #    ssi_last_error           - The error string fetched from the ssi response  
 #                               in the event of an error.  
 #  
 sub ssi_with_retries {  sub ssi_with_retries {
     my ($resource, $retries, %form) = @_;      my ($resource, $retries, %form) = @_;
     my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);      my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
Line 116  sub ssi_with_retries { Line 78  sub ssi_with_retries {
   
 sub ssi_print_error {  sub ssi_print_error {
     my ($r) = @_;      my ($r) = @_;
     $r->print('<h2>Unrecoverable network error</h2>');      my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
     $r->print('<p>Unable to perform a resource fetch from a server: <br />');      $r->print('
     $r->print("Resource: $ssi_error_resource <br />");  <br />
     $r->print("Error: $ssi_error_message <br /> Try again later.");  <h2>'.&mt('An unrecoverable network error occurred:').'</h2>
     $r->print('If errors persist, contact LonCAPA support for assistance</p>');  <p>
   '.&mt('Unable to retrieve a resource from a server:').'<br />
   '.&mt('Resource:').' '.$ssi_error_resource.'<br />
   '.&mt('Error:').' '.$ssi_error_message.'
   </p>
   <p>'.
   &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 />'.
   &mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
   '</p>');
       return;
 }  }
   
 #  #
Line 231  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 246  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>'.&mt('<b>Part: </b>[_1]',$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>'.&mt('<b>Type: </b>[_1]',$responsetype).'</td></tr>';   '<td><b>'.&mt('Type').': </b>'.$responsetype.'</td></tr>';
 #    '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>';  #    '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>';
  }   }
     }      }
Line 263  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=&ssi_with_retries($url, $ssi_retries,          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 727  sub verifyreceipt { Line 754  sub verifyreceipt {
   
     my $title.=      my $title.=
  '<h3><span class="LC_info">'.   '<h3><span class="LC_info">'.
  &mt('Verifying Submission Receipt [_1]',$receipt).   &mt('Verifying  Receipt No. [_1]',$receipt).
  '</span></h3>'."\n".   '</span></h3>'."\n".
  '<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}).   '<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}).
  '</h4>'."\n";   '</h4>'."\n";
Line 811  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;'.      my $result='<h3><span class="LC_info">&nbsp;'
  &mt($viewgrade.' Submissions for a Student or a Group of Students')   .&mt("$viewgrade Submissions for a Student or a Group of Students")
  .'</span></h3>';   .'</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 = ( 'multiple' =>      my %lt = &Apache::lonlocal::texthash (
        "Please select a student or group of students before clicking on the Next button.",   'multiple' => 'Please select a student or group of students before clicking on the Next button.',
        'single'   =>   'single'   => 'Please select the student before clicking on the Next button.',
        "Please select the student before clicking on the Next button.",       );
        );  
     %lt = &Apache::lonlocal::texthash(%lt);  
     $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 864  LISTJAVASCRIPT Line 889  LISTJAVASCRIPT
     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.      my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
  "\n".$table;   "\n".$table;
   
     $gradeTable .=       $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
  '&nbsp;'.      $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
  &mt('<b>View Problem Text: </b>[_1]',                    .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
     '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".                    .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
     '<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n".                    .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
     '<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label>').'<br />'."\n";                    .&Apache::lonhtmlcommon::row_closure();
     $gradeTable .=       $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
  '&nbsp;'.                    .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
  &mt('<b>View Answer: </b>[_1]',                    .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
     '<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n".                    .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
     '<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n".                    .&Apache::lonhtmlcommon::row_closure();
     '<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label>').'<br />'."\n";  
   
     my $submission_options;      my $submission_options;
     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {      if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
Line 890  LISTJAVASCRIPT Line 914  LISTJAVASCRIPT
  '<label><input type="radio" name="lastSub" value="last" /> '.&mt('last submission &amp; parts info').' </label>'."\n".   '<label><input type="radio" name="lastSub" value="last" /> '.&mt('last submission &amp; parts info').' </label>'."\n".
  '<label><input type="radio" name="lastSub" value="datesub" /> '.&mt('by dates and submissions').' </label>'."\n".   '<label><input type="radio" name="lastSub" value="datesub" /> '.&mt('by dates and submissions').' </label>'."\n".
  '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').'</label>';   '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').'</label>';
     $gradeTable .=       $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions'))
  '&nbsp;'.                    .$submission_options
  &mt('<b>Submissions: </b>[_1]',$submission_options).'<br />'."\n";                    .&Apache::lonhtmlcommon::row_closure();
   
       $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 .=       $gradeTable .= 
         '&nbsp;'.  
  &mt('<b>Grading Increments:</b> [_1]',  
     '<select name="increment">'.  
     '<option value="1">'.&mt('Whole Points').'</option>'.  
     '<option value=".5">'.&mt('Half Points').'</option>'.  
     '<option value=".25">'.&mt('Quarter Points').'</option>'.  
     '<option value=".1">'.&mt('Tenths of a Point').'</option>'.  
     '</select>');  
       
     $gradeTable .=   
         &build_section_inputs().          &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 915  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.=&mt('<b>Student Status:</b> [_1]',          $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.=&mt('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="'.&mt('Next-&gt;').'" /> <br />'."\n";   'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
     $gradeTable.=&check_buttons();      $gradeTable.=&check_buttons();
     $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />'.&mt('Check For Plagiarism').'</label>';  
     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);      my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
     $gradeTable.= &Apache::loncommon::start_data_table().      $gradeTable.= &Apache::loncommon::start_data_table().
  &Apache::loncommon::start_data_table_header_row();   &Apache::loncommon::start_data_table_header_row();
Line 1014  LISTJAVASCRIPT Line 1044  LISTJAVASCRIPT
  $gradeTable.= &Apache::loncommon::start_data_table_row();   $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 ne '' ?'/'.$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;'.&mt($status{$_}).'&nbsp;</td>'."\n";      $gradeTable.='<td align="center">&nbsp;'.&mt($status{$_}).'&nbsp;</td>'."\n";
  }   }
Line 1049  LISTJAVASCRIPT Line 1079  LISTJAVASCRIPT
     $gradeTable.=&Apache::loncommon::end_data_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="'.&mt('Next-&gt;').'" /></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) {
Line 1145  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 1155  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 1400  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 1432  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 1519  INNERJS Line 1551  INNERJS
     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) {
Line 1604  INNERJS Line 1636  INNERJS
     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>");
   }    }
   
Line 1671  sub gradeBox { Line 1703  sub gradeBox {
   
     my $radio.='<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) {
  $radio.= '<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";
Line 1687  sub gradeBox { Line 1719  sub gradeBox {
  $wgt.')" /></td>'."\n";   $wgt.')" /></td>'."\n";
     $line.='<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";
     $line.='<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') {
Line 1700  sub gradeBox { Line 1732  sub gradeBox {
     $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";      $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\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 .=       $result .= 
  &mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line);      '<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.='</tr></table>'."\n";
     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".      $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
Line 2084  KEYWORDS Line 2116  KEYWORDS
     $lastsubonly.="\n".'<div class="LC_grade_submission_part"><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">'.&mt('Nothing submitted - no attempts').'</span><br /><br /></div>';   '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
     next;      next;
  }   }
  foreach my $submission (@$string) {   foreach my $submission (@$string) {
Line 2125  KEYWORDS Line 2157  KEYWORDS
     ' )</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">'.&mt('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>'.&mt('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>';
     }      }
Line 2216  KEYWORDS Line 2248  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></div>');
Line 2265  KEYWORDS Line 2297  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.=&mt('[_1]student(s)',$ntstu);   $endform.=&mt('[quant,_1,student]',$ntstu);
  $endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('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="'.&mt('Next').'" '.      '<input type="button" value="'.&mt('Next').'" '.
Line 2348  sub get_last_submission { Line 2380  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 2362  sub get_last_submission { Line 2394  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 2544  sub processHandGrade { Line 2576  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 2552  sub processHandGrade { Line 2584  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 2580  sub processHandGrade { Line 2612  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 2595  sub processHandGrade { Line 2627  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 2603  sub processHandGrade { Line 2635  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 2649  sub saveHandGrade { Line 2681  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 2684  sub saveHandGrade { Line 2716  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 2711  sub saveHandGrade { Line 2743  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 2759  sub check_and_remove_from_queue { Line 2791  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 2777  sub handback_files { Line 2809  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 2785  sub handback_files { Line 2818  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 2883  sub decrement_aggs { Line 2918  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 2913  sub version_portfiles { Line 2948  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 2925  sub version_portfiles { Line 2959  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 3005  sub file_name_version_ext { Line 3040  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 3013  sub viewgrades_js { Line 3049  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 3111  sub viewgrades_js { Line 3147  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 3200  sub viewgrades { Line 3236  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';   $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='Students in no Section';          $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='Students in Section(s) [_1]';          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.=      $result.= '<h3>'.$common_header.'</h3>'.&Apache::loncommon::start_data_table();
  '<h3>'.  
  &mt("Assign Common Grade To $sectionClass",$section_display).'</h3>';  
     $result.= &Apache::loncommon::start_data_table();  
     #radio buttons/text box for assigning points for a section or class.      #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);
Line 3243  sub viewgrades { Line 3279  sub viewgrades {
  my $line = '<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";
  $line.= '<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>'.
Line 3259  sub viewgrades { Line 3295  sub viewgrades {
   
  $result.=   $result.=
     &Apache::loncommon::start_data_table_row()."\n".      &Apache::loncommon::start_data_table_row()."\n".
     &mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line).      '<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";      &Apache::loncommon::end_data_table_row()."\n";
  $ctsparts++;   $ctsparts++;
     }      }
Line 3270  sub viewgrades { Line 3306  sub viewgrades {
   
     #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>'.&mt('Assign Grade to Specific Students in '.$sectionClass,      $result.= '<h3>'.$specific_header.'</h3>'.
  $section_display).'</h3>';                &Apache::loncommon::start_data_table().
     $result.= &Apache::loncommon::start_data_table().        &Apache::loncommon::start_data_table_header_row().
  &Apache::loncommon::start_data_table_header_row().        '<th>'.&mt('No.').'</th>'.
  '<th>'.&mt('No.').'</th>'.        '<th>'.&nameUserString('header')."</th>\n";
  '<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.='<th>'.      $result.='<th>'.
Line 3438  sub editgrades { Line 3474  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++;
     }      }
Line 3452  sub editgrades { Line 3488  sub editgrades {
     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 .= '<th align="center">'.&mt('Old '.$display).'</th>'.      $display =~ s/Number of Attempts/$narrowtext/;
  '<th align="center">'.&mt('New '.$display).'</th>';      $header .= '<th align="center">'.&mt('Old').' '.$display.'</th>'.
    '<th align="center">'.&mt('New').' '.$display.'</th>';
     $columns{$partid}+=2;      $columns{$partid}+=2;
  }   }
     }      }
Line 3767  ENDPICK Line 3804  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 3797  ENDPICK Line 3834  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 3821  sub upcsvScores_form { Line 3859  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 4052  sub csvuploadassign { Line 4090  sub csvuploadassign {
    $countdone++;     $countdone++;
         }          }
     }      }
     $request->print('<br /><span class="LC_info">'.&mt("Saved [_1] students",$countdone)."</span>\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><span class="LC_warning">'.&mt('Skipped Students').'</span></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">'.&mt('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 4075  sub csvuploadassign { Line 4113  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 4115  LISTJAVASCRIPT Line 4154  LISTJAVASCRIPT
  $ctr++;   $ctr++;
     }      }
     $select.= '</select>';      $select.= '</select>';
     $result.=&mt('&nbsp;<b>Problems from:</b> [_1]',$select)."<br />\n";      $result.='&nbsp;<b>'.&mt('Problems from').':</b> '.$select."<br />\n";
   
     $ctr=0;      $ctr=0;
     foreach (@$titles) {      foreach (@$titles) {
Line 4130  LISTJAVASCRIPT Line 4169  LISTJAVASCRIPT
     my $options =      my $options =
  '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\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";   '<label><input type="radio" name="vProb" value="yes" /> '.&mt('yes').' </label>'."<br />\n";
     $result.='&nbsp;'.&mt('<b>View Problems Text: </b> [_1]',$options);      $result.='&nbsp;<b>'.&mt('View Problem Text').': </b>'.$options;
   
     $options =      $options =
  '<label><input type="radio" name="lastSub" value="none" /> '.&mt('none').' </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="datesub" checked="checked" /> '.&mt('by dates and submissions').'</label>'."\n".
  '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";   '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";
     $result.='&nbsp;'.&mt('<b>Submission Details: </b>[_1]',$options);      $result.='&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 4145  LISTJAVASCRIPT Line 4184  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;'.&mt('<b>Use CODE: [_1] </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="'.&mt('Next-&gt;').'" /><br />'."\n";   'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /><br />'."\n";
   
     $request->print($result);      $request->print($result);
   
Line 4189  LISTJAVASCRIPT Line 4226  LISTJAVASCRIPT
     }      }
     $studentTable.=&Apache::loncommon::end_data_table()."\n";      $studentTable.=&Apache::loncommon::end_data_table()."\n";
     $studentTable.='<input type="button" '.      $studentTable.='<input type="button" '.
  'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next-&gt;').'" /></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 4326  sub displayPage { Line 4363  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;'.&mt('<b>Correct answer:</b><br />[_1]',$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);
Line 4396  sub displaySubByDates { Line 4433  sub displaySubByDates {
     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">'.&mt('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 =    my $timestamp = 
     &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});      &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
Line 4443  sub displaySubByDates { Line 4481  sub displaySubByDates {
     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 />';
Line 4496  sub updateGradeByPage { Line 4535  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 4510  sub updateGradeByPage { Line 4549  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 4542  sub updateGradeByPage { Line 4581  sub updateGradeByPage {
  &Apache::loncommon::start_data_table_row().   &Apache::loncommon::start_data_table_row().
  '<td align="center" valign="top" >'.$prob.   '<td align="center" valign="top" >'.$prob.
  (scalar(@{$parts}) == 1 ? ''    (scalar(@{$parts}) == 1 ? '' 
                                         : '<br />('.&mt('[quant,_1,&nbsp;parts]',scalar(@{$parts}))                                          : '<br />('.&mt('[quant,_1,&nbsp;part]',scalar(@{$parts}))
  ).')</td>';   .')').'</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 4587  sub updateGradeByPage { Line 4626  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 4639  sub updateGradeByPage { Line 4678  sub updateGradeByPage {
   
     $studentTable.=&Apache::loncommon::end_data_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 4678  Next each scanline is checked for any er Line 4717  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 4751  sub getSequenceDropDown { Line 4790  sub getSequenceDropDown {
 }  }
   
 my %bubble_lines_per_response;     # no. bubble lines for each response.  my %bubble_lines_per_response;     # no. bubble lines for each response.
                                    # index is "symb.part_id"                                     # key is zero-based index - 0, 1, 2 ...
   
 my %first_bubble_line;             # First bubble line no. for each bubble.  my %first_bubble_line;             # First bubble line no. for each bubble.
   
Line 4792  sub restore_bubble_lines { Line 4831  sub restore_bubble_lines {
             $env{"form.scantron.responsetype.$line"};              $env{"form.scantron.responsetype.$line"};
  $line++;   $line++;
     }      }
   
 }  }
   
 #  Given the parsed scanline, get the response for   #  Given the parsed scanline, get the response for 
Line 4801  sub restore_bubble_lines { Line 4839  sub restore_bubble_lines {
 sub get_response_bubbles {  sub get_response_bubbles {
     my ($parsed_line, $response)  = @_;      my ($parsed_line, $response)  = @_;
   
   
     my $bubble_line = $first_bubble_line{$response-1} +1;      my $bubble_line = $first_bubble_line{$response-1} +1;
     my $bubble_lines= $bubble_lines_per_response{$response-1};      my $bubble_lines= $bubble_lines_per_response{$response-1};
           
Line 4825  sub get_response_bubbles { Line 4862  sub get_response_bubbles {
 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 4869  sub scantron_uploads { Line 4907  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 4914  sub scantron_CODElist { Line 5009  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 4974  sub scantron_selectphase { Line 5069  sub scantron_selectphase {
             <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>              <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>
        '.&Apache::loncommon::end_data_table_row().'         '.&Apache::loncommon::end_data_table_row().'
        '.&Apache::loncommon::start_data_table_row().'         '.&Apache::loncommon::start_data_table_row().'
             <td> '.&mt('Filename of scoring office file:').' </td><td> '.$file_selector.' </td>              <td> '.&mt('Filename of bubblesheet data file:').' </td><td> '.$file_selector.' </td>
        '.&Apache::loncommon::end_data_table_row().'         '.&Apache::loncommon::end_data_table_row().'
        '.&Apache::loncommon::start_data_table_row().'         '.&Apache::loncommon::start_data_table_row().'
             <td> '.&mt('Format of data file:').' </td><td> '.$format_selector.' </td>              <td> '.&mt('Format of bubblesheet data file:').' </td><td> '.$format_selector.' </td>
        '.&Apache::loncommon::end_data_table_row().'         '.&Apache::loncommon::end_data_table_row().'
        '.&Apache::loncommon::start_data_table_row().'         '.&Apache::loncommon::start_data_table_row().'
             <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>              <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>
Line 4995  sub scantron_selectphase { Line 5090  sub scantron_selectphase {
        '.&Apache::loncommon::end_data_table_row().'         '.&Apache::loncommon::end_data_table_row().'
        '.&Apache::loncommon::start_data_table_row().'         '.&Apache::loncommon::start_data_table_row().'
             <td colspan="2">              <td colspan="2">
               <input type="submit" value="'.&mt('Grading: Validate Scantron Records').'" />                <input type="submit" value="'.&mt('Grading: Validate Bubblesheet Records').'" />
             </td>              </td>
        '.&Apache::loncommon::end_data_table_row().'         '.&Apache::loncommon::end_data_table_row().'
     '.&Apache::loncommon::end_data_table().'      '.&Apache::loncommon::end_data_table().'
Line 5014  sub scantron_selectphase { Line 5109  sub scantron_selectphase {
     '.&Apache::loncommon::start_data_table('LC_scantron_action').'      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
        '.&Apache::loncommon::start_data_table_header_row().'         '.&Apache::loncommon::start_data_table_header_row().'
             <th>              <th>
               &nbsp;'.&mt('Specify a Scantron data file to upload.').'                &nbsp;'.&mt('Specify a bubblesheet data file to upload.').'
             </th>              </th>
        '.&Apache::loncommon::end_data_table_header_row().'         '.&Apache::loncommon::end_data_table_header_row().'
        '.&Apache::loncommon::start_data_table_row().'         '.&Apache::loncommon::start_data_table_row().'
Line 5041  sub scantron_selectphase { Line 5136  sub scantron_selectphase {
                 <input name="command" value="scantronupload_save" type="hidden" />                  <input name="command" value="scantronupload_save" type="hidden" />
                 '.&mt('File to upload: [_1]','<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="'.&mt('Upload Scantron Data').'" />                  <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
               </form>                </form>
 ');  ');
   
Line 5077  sub scantron_selectphase { Line 5172  sub scantron_selectphase {
 ');  ');
   
     &Apache::lonpickcode::code_list($r,2);      &Apache::lonpickcode::code_list($r,2);
   
       $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 5113  sub scantron_selectphase { Line 5241  sub scantron_selectphase {
       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 number 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 5140  sub scantron_selectphase { Line 5268  sub scantron_selectphase {
   
 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 5174  sub get_scantron_config { Line 5302  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 5213  sub username_to_idmap { Line 5341  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 number
                           'CODE'   -> correct the CODE                            'CODE'   -> correct the CODE
                           'answer' -> fixup the submitted answers)                            'answer' -> fixup the submitted answers)
           
Line 5387  sub digits_to_letters { Line 5515  sub digits_to_letters {
        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 5423  sub scantron_parse_scanline { Line 5551  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 6113  sub scantron_validate_file { Line 6242  sub scantron_validate_file {
     }      }
     if (!$stop) {      if (!$stop) {
  my $warning=&scantron_warning_screen('Start Grading');   my $warning=&scantron_warning_screen('Start Grading');
  $r->print(&mt('Validation process complete.').'<br />   $r->print(&mt('Validation process complete.').'<br />'.
 '.$warning.'                    $warning.
 <input type="submit" name="submit" value="'.&mt('Start Grading').'" />                    &mt('Perform verification for each student after storage of submissions?').
 <input type="hidden" name="command" value="scantron_process" />                    '&nbsp;<span class="LC_nobreak"><label>'.
 ');                    '<input type="radio" name="verifyrecord" value="1" />'.&mt('Yes').'</label>'.
                     ('&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="'.&mt('Ignore -&gt;').' " />');      $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' &rarr; " />');
     $r->print(' '.&mt('this error').' <br />');      $r->print(' '.&mt('this error').' <br />');
   
     $r->print(" <p>".&mt("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 {
             if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {              if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
         $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue -&gt;').'" onclick="javascript:verify_bubble_radio(this.form)" />');          $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' &rarr;" onclick="javascript:verify_bubble_radio(this.form)" />');
             } else {              } else {
                 $r->print('<input type="submit" name="submit" value="'.&mt('Continue -&gt;').'" />');                  $r->print('<input type="submit" name="submit" value="'.&mt('Continue').' &rarr;" />');
             }              }
     $r->print(' '.&mt('using corrected info').' <br />');      $r->print(' '.&mt('using corrected info').' <br />');
     $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />");      $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />");
Line 6511  sub scantron_validate_sequence { Line 6646  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 6584  sub scantron_validate_ID { Line 6712  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)=@_;
Line 6678  sub scantron_get_correction { Line 6777  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("      $r->print("
    <label>     <label>
        <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked />         <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i'$checked />
        ".&mt("Use the similar CODE [_1] instead.",         ".&mt("Use the similar CODE [_1] instead.",
     "<b><tt>".$testcode."</tt></b>")."      "<b><tt>".$testcode."</tt></b>")."
     </label>      </label>
Line 6692  sub scantron_get_correction { Line 6791  sub scantron_get_correction {
     }      }
  }   }
  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("      $r->print("
     <label>      <label>
         <input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked />          <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.",         ".&mt("Use the CODE [_1] that is was on the paper, ignoring the error.",
      "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."       "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
     </label>");      </label>");
Line 6726  ENDSCRIPT Line 6825  ENDSCRIPT
        ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",         ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
      "<a target='_blank' href='$href'>","</a>")."       "<a target='_blank' href='$href'>","</a>")."
     </label>       </label> 
     ".&mt("Selected CODE is [_1]","<input readonly='true' type='text' size='8' name='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />"));      ".&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("   $r->print("
Line 6750  ENDSCRIPT Line 6849  ENDSCRIPT
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my @linenums = &prompt_for_corrections($r,$question,$scan_config,      my @linenums = &prompt_for_corrections($r,$question,$scan_config,
                                                    $scan_record, $error);                                                     $scan_record, $error);
             push (@lines_to_correct,@linenums);              push(@lines_to_correct,@linenums);
  }   }
         $r->print(&verify_bubbles_checked(@lines_to_correct));          $r->print(&verify_bubbles_checked(@lines_to_correct));
     } elsif ($error eq 'missingbubble') {      } elsif ($error eq 'missingbubble') {
Line 6770  ENDSCRIPT Line 6869  ENDSCRIPT
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my @linenums = &prompt_for_corrections($r,$question,$scan_config,      my @linenums = &prompt_for_corrections($r,$question,$scan_config,
                                                    $scan_record, $error);                                                     $scan_record, $error);
             push (@lines_to_correct,@linenums);              push(@lines_to_correct,@linenums);
  }   }
         $r->print(&verify_bubbles_checked(@lines_to_correct));          $r->print(&verify_bubbles_checked(@lines_to_correct));
     } else {      } else {
Line 6919  sub prompt_for_corrections { Line 7018  sub prompt_for_corrections {
             ($responsetype_per_response{$question-1} eq 'imageresponse') ||              ($responsetype_per_response{$question-1} eq 'imageresponse') ||
             ($responsetype_per_response{$question-1} eq 'reactionresponse') ||              ($responsetype_per_response{$question-1} eq 'reactionresponse') ||
             ($responsetype_per_response{$question-1} eq 'organicresponse')) {              ($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 scantron sheets.",$lines).'<br /><br />'.&mt('A non-zero score can be assigned to the student during scantron 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 />');              $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 {          } else {
             $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");              $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");
         }          }
Line 6928  sub prompt_for_corrections { Line 7027  sub prompt_for_corrections {
         my $selected = $$scan_record{"scantron.$current_line.answer"};          my $selected = $$scan_record{"scantron.$current_line.answer"};
  &scantron_bubble_selector($r,$scan_config,$current_line,    &scantron_bubble_selector($r,$scan_config,$current_line, 
           $questionnum,$error,split('', $selected));            $questionnum,$error,split('', $selected));
         push (@linenums,$current_line);          push(@linenums,$current_line);
  $current_line++;   $current_line++;
     }      }
     if ($lines > 1) {      if ($lines > 1) {
Line 7144  sub scantron_validate_CODE { Line 7243  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 7183  sub scantron_validate_doublebubble { Line 7282  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  
    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.    
   
 =cut  
   
 sub scantron_get_maxbubble {  sub scantron_get_maxbubble {
     if (defined($env{'form.scantron_maxbubble'}) &&      if (defined($env{'form.scantron_maxbubble'}) &&
Line 7219  sub scantron_get_maxbubble { Line 7299  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   = ();      %subdivided_bubble_lines   = ();
     %responsetype_per_response = ();      %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) {
         # Need to retrieve part IDs and response IDs because essayresponse,          my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom);
         # reactionresponse and organicresponse items are not included in           if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
         # $analysis{'parts'} from lonnet::ssi.        foreach my $part_id (@{$parts}) {
         my %possible_part_ids;                   my $lines;
         if (ref($resource->parts()) eq 'ARRAY') {   
             foreach my $part (@{$resource->parts()}) {          # TODO - make this a persistent hash not an array.
                 my @resp_ids = $resource->responseIds($part);  
                 foreach my $id (@resp_ids) {                  # optionresponse, matchresponse and rankresponse type items 
                     $possible_part_ids{$part.'.'.$id} = 1;                  # render as separate sub-questions in exam mode.
                 }                  if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
             }                      ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
         }                      ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
  my $result=&ssi_with_retries($resource->src(), $ssi_retries,                      my ($numbub,$numshown);
  ('symb' => $resource->symb()),                      if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
  ('grade_target' => 'analyze'),                          if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
  ('grade_courseid' => $cid),                              $numbub = scalar(@{$analysis->{$part_id.'.options'}});
  ('grade_domain' => $udom),                          }
  ('grade_username' => $uname));                      } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
  my (undef, $an) =                          if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
     split(/_HASH_REF__/,$result, 2);                              $numbub = scalar(@{$analysis->{$part_id.'.items'}});
                           }
         my @parts;                      } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
                           if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
  my %analysis = &Apache::lonnet::str2hash($an);                              $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
                           }
         if (ref($analysis{'parts'}) eq 'ARRAY') {  
             @parts = @{$analysis{'parts'}};  
         }  
         # Add part_ids for any essayresponse items.   
         foreach my $part_id (keys(%possible_part_ids)) {  
             if (($analysis{$part_id.'.type'} eq 'essayresponse') ||  
                 ($analysis{$part_id.'.type'} eq 'reactionresponse') ||  
                 ($analysis{$part_id.'.type'} eq 'organicresponse')) {  
                 if (!grep(/^\Q$part_id\E$/,@parts)) {  
                     push (@parts,$part_id);  
                 }  
             }  
         }  
   
  foreach my $part_id (@parts) {  
             my $lines = $analysis{"$part_id.bubble_lines"};  
   
     # TODO - make this a persistent hash not an array.  
   
             # optionresponse, matchresponse and rankresponse type items   
             # render as separate sub-questions in exam mode.  
             if (($analysis{$part_id.'.type'} eq 'optionresponse') ||  
                 ($analysis{$part_id.'.type'} eq 'matchresponse') ||  
                 ($analysis{$part_id.'.type'} eq 'rankresponse')) {  
                 my ($numbub,$numshown);  
                 if ($analysis{$part_id.'.type'} eq 'optionresponse') {  
                     if (ref($analysis{$part_id.'.options'}) eq 'ARRAY') {  
                         $numbub = scalar(@{$analysis{$part_id.'.options'}});  
                     }                      }
                 } elsif ($analysis{$part_id.'.type'} eq 'matchresponse') {                      if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
                     if (ref($analysis{$part_id.'.items'}) eq 'ARRAY') {                          $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
                         $numbub = scalar(@{$analysis{$part_id.'.items'}});  
                     }                      }
                 } elsif ($analysis{$part_id.'.type'} eq 'rankresponse') {                      my $bubbles_per_line = 10;
                     if (ref($analysis{$part_id.'.foils'}) eq 'ARRAY') {                      my $inner_bubble_lines = int($numbub/$bubbles_per_line);
                         $numbub = scalar(@{$analysis{$part_id.'.foils'}});                      if (($numbub % $bubbles_per_line) != 0) {
                           $inner_bubble_lines++;
                     }                      }
                 }                      for (my $i=0; $i<$numshown; $i++) {
                 if (ref($analysis{$part_id.'.shown'}) eq 'ARRAY') {                          $subdivided_bubble_lines{$response_number} .= 
                     $numshown = scalar(@{$analysis{$part_id.'.shown'}});                              $inner_bubble_lines.',';
                 }                      }
                 my $bubbles_per_line = 10;                      $subdivided_bubble_lines{$response_number} =~ s/,$//;
                 my $inner_bubble_lines = int($numshown/$bubbles_per_line);                      $lines = $numshown * $inner_bubble_lines;
                 if (($numshown % $bubbles_per_line) != 0) {                  } else {
                     $inner_bubble_lines++;                      $lines = $analysis->{"$part_id.bubble_lines"};
                 }                  } 
                 for (my $i=0; $i<$numshown; $i++) {  
                     $subdivided_bubble_lines{$response_number} .=   
                         $inner_bubble_lines.',';  
                 }  
                 $subdivided_bubble_lines{$response_number} =~ s/,$//;  
             }   
   
             $first_bubble_line{$response_number} = $bubble_line;  
     $bubble_lines_per_response{$response_number} = $lines;  
             $responsetype_per_response{$response_number} =   
                 $analysis{$part_id.'.type'};  
     $response_number++;  
   
     $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 7328  sub scantron_get_maxbubble { Line 7374  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 7391  sub scantron_validate_missingbubbles { Line 7427  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) = @_;
Line 7432  sub scantron_process_students { Line 7445  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 7441  SCANTRONFORM Line 7471  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      # If an ssi failed in scantron_get_maxbubble, put an error message out to
     # the user and return.      # the user and return.
Line 7463  SCANTRONFORM Line 7494  SCANTRONFORM
  $r->print("</form>");   $r->print("</form>");
  &ssi_print_error($r);   &ssi_print_error($r);
  $r->print(&show_grading_menu_form($symb));   $r->print(&show_grading_menu_form($symb));
           &Apache::lonnet::remove_lock($lock);
  return ''; # Dunno why the other returns return '' rather than just returning.   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 7491  SCANTRONFORM Line 7526  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);
   
Line 7498  SCANTRONFORM Line 7546  SCANTRONFORM
     &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=&ssi_with_retries($resource->src(), $ssi_retries, %form);          }
     if ($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));  
  return ''; # Why return ''?  Beats me.  
     }  
   
     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 7540  SCANTRONFORM Line 7647  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('      $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>
   
   <h3>'.&mt('Send scanned bubblesheet data to a course').'</h3>
   
 <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.
 <table>    &Apache::lonhtmlcommon::start_pick_box().
 <tr><td>'.$select_link.'                             </td></tr>    &Apache::lonhtmlcommon::row_title(&mt('Course ID')).
 <tr><td>'.&mt('Course ID:').'     </td>    '<input name="courseid" type="text" size="30" />'.$select_link.
     <td><input name="courseid"   type="text" />      </td></tr>    &Apache::lonhtmlcommon::row_closure().
 <tr><td>'.&mt('Course Name:').'   </td>    &Apache::lonhtmlcommon::row_title(&mt('Course Name')).
     <td><input name="coursename" type="text" />      </td></tr>    '<input name="coursename" type="text" size="30" />'.$syllabuslink.
 <tr><td>'.&mt('Domain:').'        </td>    &Apache::lonhtmlcommon::row_closure().
     <td>'.$domsel.'                                  </td></tr>    &Apache::lonhtmlcommon::row_title(&mt('Domain')).
 <tr><td>'.&mt('File to upload:').'</td>    '<input name="domainid" type="hidden" />'.$domdesc.
     <td><input type="file" name="upfile" size="50" /></td></tr>    &Apache::lonhtmlcommon::row_closure().
 </table>    &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 name="command" value="scantronupload_save" type="hidden" />
 <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Scantron Data').'" />  <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Scantron Data').'" />
 </form>  </form>
Line 7588  sub scantron_upload_scantron_data { Line 7766  sub scantron_upload_scantron_data {
     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 7617  sub scantron_upload_scantron_data_save { Line 7787  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(&mt("Doing upload to [_1]",$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(&mt("<span class=\"LC_error\">Error:</span> The file you attempted to upload, [_1]  contained no information. Please check that you entered the correct filename.",'<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</span>"));          $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(&mt("<span class=\"LC_success\">Success:</span> Successfully uploaded [_1] bytes of data into location [_2]",                                              $env{'form.courseid'},$env{'form.domainid'});
   (length($env{'form.upfile'})-1),   if ($result =~ m{^/uploaded/}) {
   '<span class="LC_filename">'.$result."</span>"));      $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(&mt("<span class=\"LC_error\">Error:</span> An error ([_1]) occurred when attempting to upload the file, [_2]",      $r->print(&mt('[_1]Error:[_2] An error ([_3]) occurred when attempting to upload the file, [_4]',
   $result,                            '<span class="LC_error">','</span>',$result,
   '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</span>"));    '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
   
  }   }
     }      }
     if ($symb) {      if ($symb) {
Line 7657  sub scantron_upload_scantron_data_save { Line 7816  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 7673  sub valid_file { Line 7910  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 7722  sub scantron_download_scantron_data { Line 7949  sub scantron_download_scantron_data {
     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 7773  sub grading_menu { Line 8279  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 7816  sub grading_menu { Line 8335  sub grading_menu {
  '<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 .='<hr /><input type="button" value="'.&mt('Verify Receipt').'" '.  
                 $menudata->{'jscript'}.  
                 ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.  
                 ' /> '.  
  &Apache::lonnet::recprefix($env{'request.course.id'}).  
                     '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />';  
         }  
         $Str .= '    '.('&nbsp;'x8).$menudata->{'short_description'}.  
             "\n";  
     }  
     $Str .="</form>\n";      $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 7862  sub grading_menu { Line 8372  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 7883  sub submit_options { Line 8393  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 7910  sub submit_options { Line 8421  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 7929  GRADINGMENUJS Line 8440  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 7939  GRADINGMENUJS Line 8459  GRADINGMENUJS
  '<input type="hidden" name="showgrading" value="yes" />'."\n";   '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
     $result.='      $result.='
     <div class="LC_grade_select_mode">  <h2>
       <div class="LC_grade_select_mode_current">    '.&mt('Grade Current Resource').'
         <h2>  </h2>
           '.&mt('Grade Current Resource').'  <div>
         </h2>    '.$table.'
         <div class="LC_grade_select_mode_body">  </div>
           <div class="LC_grades_resource_info">  
            '.$table.'  <div class="LC_columnSection">
           </div>    
           <div class="LC_grade_select_mode_selector">      <fieldset>
              <div class="LC_grade_select_mode_selector_header">        <legend>
                 '.&mt('Sections').'         '.&mt('Sections').'
              </div>        </legend>
              <div class="LC_grade_select_mode_selector_body">        <select name="section" multiple="multiple" size="5">'."\n";
        <select name="section" multiple="multiple" size="5">'."\n";      $result.= $selsec;
     if (ref($sections)) {  
  foreach my $section (sort (@$sections)) {  
     $result.='<option value="'.$section.'" '.  
  ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";  
  }  
     }  
     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';      $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
     $result.='      $result.='
              </div>      </fieldset>
           </div>    
           <div class="LC_grade_select_mode_selector">      <fieldset>
              <div class="LC_grade_select_mode_selector_header">        <legend>
                 '.&mt('Groups').'          '.&mt('Groups').'
              </div>        </legend>
              <div class="LC_grade_select_mode_selector_body">        '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
                 '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'      </fieldset>
              </div>    
           </div>      <fieldset>
           <div class="LC_grade_select_mode_selector">        <legend>
              <div class="LC_grade_select_mode_selector_header">          '.&mt('Access Status').'
                 '.&mt('Access Status').'        </legend>
              </div>        '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'
              <div class="LC_grade_select_mode_selector_body">      </fieldset>
                 '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'    
              </div>      <fieldset>
           </div>        <legend>
           <div class="LC_grade_select_mode_selector">          '.&mt('Submission Status').'
              <div class="LC_grade_select_mode_selector_header">        </legend>
                 '.&mt('Submission Status').'        <select name="submitonly" size="5">
              </div>  
              <div class="LC_grade_select_mode_selector_body">  
                <select name="submitonly" size="5">  
          <option value="yes" '.      ($saveSub eq 'yes'       ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>           <option value="yes" '.      ($saveSub eq 'yes'       ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>
          <option value="queued" '.   ($saveSub eq 'queued'    ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>           <option value="queued" '.   ($saveSub eq 'queued'    ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>
          <option value="graded" '.   ($saveSub eq 'graded'    ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>           <option value="graded" '.   ($saveSub eq 'graded'    ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>
          <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>           <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>
                  <option value="all" '.      ($saveSub eq 'all'       ? 'selected="selected"' : '').'>'.&mt('with any status').'</option>                   <option value="all" '.      ($saveSub eq 'all'       ? 'selected="selected"' : '').'>'.&mt('with any status').'</option>
                </select>        </select>
              </div>      </fieldset>
           </div>    
           <div class="LC_grade_select_mode_type_body">  </div>
             <div class="LC_grade_select_mode_type">  
   <br />
             <div>
               <div>
               <label>                <label>
                 <input type="radio" name="radioChoice" value="submission" '.                  <input type="radio" name="radioChoice" value="submission" '.
                   ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.                    ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.
              &mt('Select individual students to grade and view submissions.').'               &mt('Select individual students to grade and view submissions.').'
       </label>         </label> 
             </div>              </div>
             <div class="LC_grade_select_mode_type">              <div>
       <label>        <label>
                 <input type="radio" name="radioChoice" value="viewgrades" '.                  <input type="radio" name="radioChoice" value="viewgrades" '.
                   ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.                    ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
                     &mt('Grade all selected students in a grading table.').'                      &mt('Grade all selected students in a grading table.').'
               </label>                </label>
             </div>              </div>
             <div class="LC_grade_select_mode_type">              <div>
       <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next-&gt;').'" />        <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next').' &rarr;" />
             </div>              </div>
           </div>            </div>
         </div>  
       </div>  
       <div class="LC_grade_select_mode_page">  
         <h2>          <h2>
          '.&mt('Grade Complete Folder for One Student').'           '.&mt('Grade Complete Folder for One Student').'
         </h2>          </h2>
         <div class="LC_grades_select_mode_body">          <div>
           <div class="LC_grade_select_mode_type_body">              <div>
             <div class="LC_grade_select_mode_type">  
               <label>                <label>
                 <input type="radio" name="radioChoice" value="pickStudentPage" '.                  <input type="radio" name="radioChoice" value="pickStudentPage" '.
   ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.    ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
   &mt('The <b>complete</b> page/sequence/folder: For one student').'    &mt('The <b>complete</b> page/sequence/folder: For one student').'
               </label>                </label>
             </div>              </div>
             <div class="LC_grade_select_mode_type">              <div>
       <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next-&gt;').'" />        <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next').' &rarr;" />
             </div>              </div>
           </div>  
         </div>          </div>
       </div>  
     </div>  
   </form>';    </form>';
     $result .= &show_grading_menu_form($symb);      $result .= &show_grading_menu_form($symb);
     return $result;      return $result;
Line 8133  sub process_clicker { Line 8642  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 8146  sub process_clicker { Line 8655  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 8157  sub process_clicker { Line 8666  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 8210  function sanitycheck() { Line 8721  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 8240  sub process_clicker_file { Line 8754  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 8258  sub process_clicker_file { Line 8785  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 8303  sub process_clicker_file { Line 8832  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 8318  ENDHEADER Line 8850  ENDHEADER
              &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 8361  ENDHEADER Line 8897  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 8432  sub interwrite_eval { Line 8968  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 8506  ENDHEADER Line 9042  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 8519  ENDHEADER Line 9060  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 8537  ENDHEADER Line 9078  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 8563  sub handler { Line 9104  sub handler {
     }      }
   
     $ssi_error = 0;      $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 8658  sub handler { Line 9201  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) {      if ($ssi_error) {
Line 8673  sub handler { Line 9218  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.514  
changed lines
  Added in v.1.572


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