Diff for /loncom/homework/grades.pm between versions 1.528.2.6 and 1.533

version 1.528.2.6, 2008/12/31 21:08:09 version 1.533, 2008/12/08 18:25:13
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 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.  
 #  
 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 2358  sub get_last_submission { Line 2320  sub get_last_submission {
  $$returnhash{$version.':keys'}))) {   $$returnhash{$version.':keys'}))) {
  $lasthash{$key}=$$returnhash{$version.':'.$key};   $lasthash{$key}=$$returnhash{$version.':'.$key};
  $timestamp =    $timestamp = 
     &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});      scalar(localtime($$returnhash{$version.':timestamp'}));
     }      }
  }   }
  foreach my $key (keys(%lasthash)) {   foreach my $key (keys(%lasthash)) {
Line 2796  sub handback_files { Line 2758  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('<br /><span class="LC_error">'.                          $request->print('<span class="LC_error">An error occurred ('.$result.
                             &mt('An error occurred ([_1]) while trying to upload [_2].',                          ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</span><br />');
                                 $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 6613  sub scantron_validate_sequence { Line 6573  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 6686  sub scantron_validate_ID { Line 6639  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 7285  sub scantron_validate_doublebubble { Line 7209  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 7333  sub scantron_get_maxbubble { Line 7238  sub scantron_get_maxbubble {
     my $response_number = 0;      my $response_number = 0;
     my $bubble_line     = 0;      my $bubble_line     = 0;
     foreach my $resource (@resources) {      foreach my $resource (@resources) {
         my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom);          my $symb = $resource->symb();
         if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {  
             foreach my $part_id (@{$parts}) {  
   
                 my $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.'.items'}) eq 'ARRAY') {  
                             $numbub = scalar(@{$analysis->{$part_id.'.items'}});  
                         }  
                     } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {  
                         if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {  
                             $numbub = scalar(@{$analysis->{$part_id.'.foils'}});  
                         }  
                     }  
                     if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {  
                         $numshown = scalar(@{$analysis->{$part_id.'.shown'}});  
                     }  
                     my $bubbles_per_line = 10;  
                     my $inner_bubble_lines = int($numbub/$bubbles_per_line);  
                     if (($numbub % $bubbles_per_line) != 0) {  
                         $inner_bubble_lines++;  
                     }  
                     for (my $i=0; $i<$numshown; $i++) {  
                         $subdivided_bubble_lines{$response_number} .=   
                             $inner_bubble_lines.',';  
                     }  
                     $subdivided_bubble_lines{$response_number} =~ s/,$//;  
                     $lines = $numshown * $inner_bubble_lines;  
                 } else {  
                     $lines = $analysis->{"$part_id.bubble_lines"};  
                 }   
   
                 $first_bubble_line{$response_number} = $bubble_line;          my (@parts,@allparts,@possible_parts);
         $bubble_lines_per_response{$response_number} = $lines;  
                 $responsetype_per_response{$response_number} =   
                     $analysis->{$part_id.'.type'};  
         $response_number++;  
   
         $bubble_line +=  $lines;          # Need to retrieve part IDs and response IDs because essayresponse,
         $total_lines +=  $lines;          # reactionresponse and organicresponse items are not included in 
     }          # $analysis{'parts'} from lonnet::ssi.  
           if (ref($resource->parts()) eq 'ARRAY') {
               foreach my $part (@{$resource->parts()}) {
                   if (!&Apache::loncommon::check_if_partid_hidden($part,$symb,$udom,$uname)) {
                       my @resp_ids = $resource->responseIds($part);
                       foreach my $id (@resp_ids) {
                           my $part_id = $part.'.'.$id;
                           push(@possible_parts,$part_id);
                       }
                   }
               }
         }          }
     }  
     &Apache::lonnet::delenv('scantron\.');  
   
     &save_bubble_lines();  
     $env{'form.scantron_maxbubble'} =  
  $total_lines;  
     return $env{'form.scantron_maxbubble'};  
 }  
   
 sub scantron_partids_tograde {  
     my ($resource,$cid,$uname,$udom) = @_;  
     my (%analysis,@parts);  
   
     if (ref($resource)) {  
         my $symb = $resource->symb();  
         my $result=&ssi_with_retries($resource->src(), $ssi_retries,          my $result=&ssi_with_retries($resource->src(), $ssi_retries,
                                         ('symb' => $symb,                                          ('symb' => $symb,
                                          'grade_target' => 'analyze',                                           'grade_target' => 'analyze',
                                          'grade_courseid' => $cid,                                           'grade_courseid' => $cid,
                                          'grade_domain' => $udom,                                           'grade_domain' => $udom,
                                          'grade_username' => $uname));                                           'grade_username' => $uname));
         my (undef, $an) = split(/_HASH_REF__/,$result, 2);          my (undef, $an) =
         %analysis = &Apache::lonnet::str2hash($an);              split(/_HASH_REF__/,$result, 2);
   
    my %analysis = &Apache::lonnet::str2hash($an);
   
         if (ref($analysis{'parts'}) eq 'ARRAY') {          if (ref($analysis{'parts'}) eq 'ARRAY') {
             foreach my $part (@{$analysis{'parts'}}) {              foreach my $part (@{$analysis{'parts'}}) {
Line 7420  sub scantron_partids_tograde { Line 7276  sub scantron_partids_tograde {
                 }                  }
             }              }
         }          }
     }          # Add part_ids for any essayresponse, reactionresponse or 
     return (\%analysis,\@parts);          # organicresponse items. 
 }          foreach my $part_id (@possible_parts) {
               if (grep(/^\Q$part_id\E$/,@parts)) {
                   push(@allparts,$part_id);
               } else {
                   if (($analysis{$part_id.'.type'} eq 'essayresponse') ||
                       ($analysis{$part_id.'.type'} eq 'reactionresponse') ||
                       ($analysis{$part_id.'.type'} eq 'organicresponse')) {
                       push(@allparts,$part_id);
                   }
               }
           }
   
 =pod   foreach my $part_id (@allparts) {
               my $lines;
   
 =item scantron_validate_missingbubbles      # TODO - make this a persistent hash not an array.
   
    Validates all scanlines in the selected file to not have any              # optionresponse, matchresponse and rankresponse type items 
     answers that don't have bubbles that have not been verified              # render as separate sub-questions in exam mode.
     to be bubble free.              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.'.items'}) eq 'ARRAY') {
                           $numbub = scalar(@{$analysis{$part_id.'.items'}});
                       }
                   } elsif ($analysis{$part_id.'.type'} eq 'rankresponse') {
                       if (ref($analysis{$part_id.'.foils'}) eq 'ARRAY') {
                           $numbub = scalar(@{$analysis{$part_id.'.foils'}});
                       }
                   }
                   if (ref($analysis{$part_id.'.shown'}) eq 'ARRAY') {
                       $numshown = scalar(@{$analysis{$part_id.'.shown'}});
                   }
                   my $bubbles_per_line = 10;
                   my $inner_bubble_lines = int($numbub/$bubbles_per_line);
                   if (($numbub % $bubbles_per_line) != 0) {
                       $inner_bubble_lines++;
                   }
                   for (my $i=0; $i<$numshown; $i++) {
                       $subdivided_bubble_lines{$response_number} .= 
                           $inner_bubble_lines.',';
                   }
                   $subdivided_bubble_lines{$response_number} =~ s/,$//;
                   $lines = $numshown * $inner_bubble_lines;
               } else {
                   $lines = $analysis{"$part_id.bubble_lines"};
               } 
   
               $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;
       $total_lines +=  $lines;
    }
   
       }
       &Apache::lonnet::delenv('scantron\.');
   
       &save_bubble_lines();
       $env{'form.scantron_maxbubble'} =
    $total_lines;
       return $env{'form.scantron_maxbubble'};
   }
   
 =cut  
   
 sub scantron_validate_missingbubbles {  sub scantron_validate_missingbubbles {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
Line 7487  sub scantron_validate_missingbubbles { Line 7405  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 7528  sub scantron_process_students { Line 7423  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);
   
     my ($uname,$udom,%partids_by_symb);  
     foreach my $resource (@resources) {  
         my $ressymb = $resource->symb();  
         my ($analysis,$parts) =  
             &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom);  
         $partids_by_symb{$ressymb} = $parts;  
     }  
 #    $r->print("geto ".scalar(@resources)."<br />");  #    $r->print("geto ".scalar(@resources)."<br />");
     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">
Line 7545  SCANTRONFORM Line 7432  SCANTRONFORM
     $r->print($result);      $r->print($result);
   
     my @delayqueue;      my @delayqueue;
     my (%completedstudents,,%scandata);      my %completedstudents;
           
     my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));      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);
Line 7554  SCANTRONFORM Line 7441  SCANTRONFORM
     '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 $started;      my ($uname,$udom,$started);
   
     &scantron_get_maxbubble(); # Need the bubble lines array to parse.      &scantron_get_maxbubble(); # Need the bubble lines array to parse.
           
Line 7573  SCANTRONFORM Line 7459  SCANTRONFORM
  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 7607  SCANTRONFORM Line 7490  SCANTRONFORM
  if (&scantron_clear_skip($scanlines,$scan_data,$i)) {   if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
     &scantron_putfile($scanlines,$scan_data);      &scantron_putfile($scanlines,$scan_data);
  }   }
   
    my $i=0;
    foreach my $resource (@resources) {
       $i++;
       my %form=('submitted'     =>'scantron',
         'grade_target'  =>'grade',
         'grade_username'=>$uname,
         'grade_domain'  =>$udom,
         'grade_courseid'=>$env{'request.course.id'},
         'grade_symb'    =>$resource->symb());
       if (exists($scan_record->{'scantron.CODE'})
    && 
    &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {
    $form{'CODE'}=$scan_record->{'scantron.CODE'};
       } else {
    $form{'CODE'}='';
       } 
       my $result=&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));
                   &Apache::lonnet::remove_lock($lock);
    return ''; # Why return ''?  Beats me.
       }
   
         my $scancode;      if (&Apache::loncommon::connection_aborted($r)) { last; }
         if ((exists($scan_record->{'scantron.CODE'})) &&   }
             (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {  
             $scancode = $scan_record->{'scantron.CODE'};  
         } else {  
             $scancode = '';  
         }  
   
         if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,  
                                    @resources) 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);  
             return '';      # Why return ''?  Beats me.  
         }  
   
  $completedstudents{$uname}={'line'=>$line};   $completedstudents{$uname}={'line'=>$line};
         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) {  
                 ($counter,my $recording) =  
                     &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},  
                                              $counter,$studentdata,\%partids_by_symb,  
                                              \%scantron_config,\%lettdig,$numletts);  
                 $studentrecord .= $recording;  
             }  
             if ($studentrecord ne $studentdata) {  
                 $counter = -1;  
                 $studentrecord = '';  
                 foreach my $resource (@resources) {  
                     ($counter,my $recording) =  
                         &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},  
                                                  $counter,$studentdata,\%partids_by_symb,  
                                                  \%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; }   if (&Apache::loncommon::connection_aborted($r)) { last; }
     } continue {      } continue {
  &Apache::lonxml::clear_problem_counter();   &Apache::lonxml::clear_problem_counter();
Line 7696  SCANTRONFORM Line 7535  SCANTRONFORM
     return '';      return '';
 }  }
   
 sub grade_student_bubbles {  
     my ($r,$uname,$udom,$scan_record,$scancode,@resources) = @_;  
     foreach my $resource (@resources) {  
         my %form = ('submitted'     => 'scantron',  
                     'grade_target'  => 'grade',  
                     'grade_username'=> $uname,  
                     'grade_domain'  => $udom,  
                     'grade_courseid'=> $env{'request.course.id'},  
                     'grade_symb'    => $resource->symb(),  
                     'code'          => $scancode);  
         my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form);  
         return 'ssi_error' if ($ssi_error);  
         last if (&Apache::loncommon::connection_aborted($r));  
     }  
     return;  
 }  
   
 =pod  
   
 =item scantron_upload_scantron_data  
   
     Creates the screen for adding a new bubble sheet data file to a course.  
   
 =cut  
   
 sub scantron_upload_scantron_data {  sub scantron_upload_scantron_data {
     my ($r)=@_;      my ($r)=@_;
     $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));      $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
Line 7761  sub scantron_upload_scantron_data { Line 7575  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 7830  sub scantron_upload_scantron_data_save { Line 7636  sub scantron_upload_scantron_data_save {
     return '';      return '';
 }  }
   
 =pod  
   
 =item valid_file  
   
    Validates that the requested bubble data file exists in the course.  
   
 =cut  
   
 sub valid_file {  sub valid_file {
     my ($requested_file)=@_;      my ($requested_file)=@_;
     foreach my $filename (sort(&scantron_filenames())) {      foreach my $filename (sort(&scantron_filenames())) {
Line 7846  sub valid_file { Line 7644  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 7901  sub checkscantron_results { Line 7689  sub checkscantron_results {
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $grading_menu_button=&show_grading_menu_form($symb);      my $grading_menu_button=&show_grading_menu_form($symb);
     my $cid = $env{'request.course.id'};      my $cid = $env{'request.course.id'};
     my %lettdig = &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,
                     );
     my $numletts = scalar(keys(%lettdig));      my $numletts = scalar(keys(%lettdig));
     my $cnum = $env{'course.'.$cid.'.num'};      my $cnum = $env{'course.'.$cid.'.num'};
     my $cdom = $env{'course.'.$cid.'.domain'};      my $cdom = $env{'course.'.$cid.'.domain'};
Line 7915  sub checkscantron_results { Line 7714  sub checkscantron_results {
     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,undef,1,0);      my @resources=$navmap->retrieveResources($map,undef,1,0);
     my ($uname,$udom,%partids_by_symb);  
     foreach my $resource (@resources) {  
         my $ressymb = $resource->symb();  
         my ($analysis,$parts) =  
             &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom);  
         $partids_by_symb{$ressymb} = $parts;  
     }  
     my (%scandata,%lastname,%bylast);      my (%scandata,%lastname,%bylast);
     $r->print('      $r->print('
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");
Line 7975  sub checkscantron_results { Line 7767  sub checkscantron_results {
         $scandata{$pid} =~ s/\r$//;          $scandata{$pid} =~ s/\r$//;
         ($username,$domain)=split(/:/,$uname);          ($username,$domain)=split(/:/,$uname);
         my $counter = -1;          my $counter = -1;
           my (%expected,%startpos);
         foreach my $resource (@resources) {          foreach my $resource (@resources) {
             ($counter,my $recording) =              next if (!$resource->is_problem());
                 &verify_scantron_grading($resource,$domain,$username,$cid,$counter,              my $symb = $resource->symb();
                                          $scandata{$pid},\%partids_by_symb,              my $partsref = $resource->parts();
                                          \%scantron_config,\%lettdig,$numletts);              my @parts;
             $record{$pid} .= $recording;              my @part_ids = ();
               if (ref($partsref) eq 'ARRAY') {
                  @parts = @{$partsref};
                  foreach my $part (@parts) {
                      my @resp_ids = $resource->responseIds($part);
                      foreach my $resp (@resp_ids) {
                          $counter ++;
                          my $part_id = $part.'.'.$resp;
                          $expected{$part_id} = 0;
                          push(@part_ids,$part_id);
                          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} .= $;
                                       }
                                   }
                               } 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{$pid},$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 (@part_ids) {
                       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{$pid} .= $recorded{$part_id};
                   }
               }
         }          }
     }      }
     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);      &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
Line 8042  sub checkscantron_results { Line 7948  sub checkscantron_results {
     return;      return;
 }  }
   
 sub verify_scantron_grading {  
     my ($resource,$domain,$username,$cid,$counter,$scandata,$partids_by_symb,  
         $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_by_symb) ne 'HASH');  
     return ($counter,$record) if (ref($partids_by_symb->{$symb}) ne 'ARRAY');  
     foreach my $part_id (@{$partids_by_symb->{$symb}}) {  
         $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_by_symb->{$symb}}) {  
             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;  
 }  
   
 =pod  
   
 =back  
   
 =cut  
   
 #-------- end of section for handling grading scantron forms -------  #-------- end of section for handling grading scantron forms -------
 #  #
Line 8381  GRADINGMENUJS Line 8150  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 8391  GRADINGMENUJS Line 8169  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="columnSection">
           </div>    <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>
           <div class="LC_grade_select_mode_selector">  
              <div class="LC_grade_select_mode_selector_header">    <div>
                 '.&mt('Groups').'      <fieldset>
              </div>        <legend>
              <div class="LC_grade_select_mode_selector_body">          '.&mt('Groups').'
                 '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'        </legend>
              </div>        '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
           </div>      </fieldset>
           <div class="LC_grade_select_mode_selector">    </div>
              <div class="LC_grade_select_mode_selector_header">  
                 '.&mt('Access Status').'    <div>
              </div>      <fieldset>
              <div class="LC_grade_select_mode_selector_body">        <legend>
                 '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'          '.&mt('Access Status').'
              </div>        </legend>
           </div>        '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'
           <div class="LC_grade_select_mode_selector">      </fieldset>
              <div class="LC_grade_select_mode_selector_header">    </div>
                 '.&mt('Submission Status').'  
              </div>    <div>
              <div class="LC_grade_select_mode_selector_body">      <fieldset>
                <select name="submitonly" size="5">        <legend>
           '.&mt('Submission Status').'
         </legend>
         <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>
           <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-&gt;').'" />
             </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-&gt;').'" />
             </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 9159  sub handler { Line 8932  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 IDs
   
   =back
   
   =cut

Removed from v.1.528.2.6  
changed lines
  Added in v.1.533


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