Diff for /loncom/homework/grades.pm between versions 1.512 and 1.529

version 1.512, 2008/03/03 23:36:30 version 1.529, 2008/11/11 16:40:47
Line 26 Line 26
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
   
   =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 OTHER 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
   
 package Apache::grades;  package Apache::grades;
 use strict;  use strict;
 use Apache::style;  use Apache::style;
Line 47  use LONCAPA; Line 224  use LONCAPA;
 use POSIX qw(floor);  use POSIX qw(floor);
   
   
   
 my %perm=();  my %perm=();
   
   #  These variables are used to recover from ssi errors
   
   my $ssi_retries = 5;
   my $ssi_error;
   my $ssi_error_resource;
   my $ssi_error_message;
   
   
   sub ssi_with_retries {
       my ($resource, $retries, %form) = @_;
       my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
       if ($response->is_error) {
    $ssi_error          = 1;
    $ssi_error_resource = $resource;
    $ssi_error_message  = $response->code . " " . $response->message;
       }
   
       return $content;
   
   }
   #
   #  Prodcuces an ssi retry failure error message to the user:
   #
   
   sub ssi_print_error {
       my ($r) = @_;
       my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
       $r->print('
   <br />
   <h2>'.&mt('An unrecoverable network error occurred:').'</h2>
   <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;
   }
   
 #  #
 # --- Retrieve the parts from the metadata file.---  # --- Retrieve the parts from the metadata file.---
 sub getpartlist {  sub getpartlist {
Line 157  sub showResourceInfo { Line 377  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 195  sub reset_caches { Line 415  sub reset_caches {
     }      }
   
     sub get_analyze {      sub get_analyze {
  my ($symb,$uname,$udom)=@_;   my ($symb,$uname,$udom,$no_increment)=@_;
  my $key = "$symb\0$uname\0$udom";   my $key = "$symb\0$uname\0$udom";
  return $analyze_cache{$key} if (exists($analyze_cache{$key}));   return $analyze_cache{$key} if (exists($analyze_cache{$key}));
   
  my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);   my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
  $url=&Apache::lonnet::clutter($url);   $url=&Apache::lonnet::clutter($url);
  my $subresult=&Apache::lonnet::ssi($url,   my $subresult=&ssi_with_retries($url, $ssi_retries,
    ('grade_target' => 'analyze'),     ('grade_target' => 'analyze',
    ('grade_domain' => $udom),      'grade_domain' => $udom,
    ('grade_symb' => $symb),      'grade_symb' => $symb,
    ('grade_courseid' =>       'grade_courseid' => 
     $env{'request.course.id'}),      $env{'request.course.id'},
    ('grade_username' => $uname));      'grade_username' => $uname,
                                               'grade_noincrement' => $no_increment));
  (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);
  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"};
     }      }
   
Line 947  LISTJAVASCRIPT Line 1168  LISTJAVASCRIPT
        '&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 1759  sub download_all_link { Line 1980  sub download_all_link {
  join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));   join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
   
     my $identifier = &Apache::loncommon::get_cgi_id();      my $identifier = &Apache::loncommon::get_cgi_id();
     &Apache::lonnet::appenv('cgi.'.$identifier.'.students' => $all_students,      &Apache::lonnet::appenv({'cgi.'.$identifier.'.students' => $all_students,
                             'cgi.'.$identifier.'.symb' => $symb,                               'cgi.'.$identifier.'.symb' => $symb,
                             'cgi.'.$identifier.'.parts' => $parts,);                               'cgi.'.$identifier.'.parts' => $parts,});
     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.      $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
       &mt('Download All Submitted Documents').'</a>');        &mt('Download All Submitted Documents').'</a>');
     return      return
Line 2142  KEYWORDS Line 2363  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 2470  sub processHandGrade { Line 2691  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 2478  sub processHandGrade { Line 2699  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 2506  sub processHandGrade { Line 2727  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 2521  sub processHandGrade { Line 2742  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 2529  sub processHandGrade { Line 2750  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 2575  sub saveHandGrade { Line 2796  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 2610  sub saveHandGrade { Line 2831  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 2637  sub saveHandGrade { Line 2858  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 2685  sub check_and_remove_from_queue { Line 2906  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 2703  sub handback_files { Line 2924  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 2809  sub decrement_aggs { Line 3031  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 2839  sub version_portfiles { Line 3061  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 2851  sub version_portfiles { Line 3072  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 3210  sub viewgrades { Line 3432  sub viewgrades {
  $display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower   $display =~ s|^Number of Attempts|Tries<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 3364  sub editgrades { Line 3586  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 4326  sub displaySubByDates { Line 4548  sub displaySubByDates {
     }      }
   
     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 4369  sub displaySubByDates { Line 4592  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 4422  sub updateGradeByPage { Line 4646  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 4436  sub updateGradeByPage { Line 4660  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 4468  sub updateGradeByPage { Line 4692  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 4513  sub updateGradeByPage { Line 4737  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 4565  sub updateGradeByPage { Line 4789  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 4751  sub get_response_bubbles { Line 4975  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 4795  sub scantron_uploads { Line 5020  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 4881  sub scantron_selectphase { Line 5163  sub scantron_selectphase {
     my $CODE_unique=&scantron_CODEunique();      my $CODE_unique=&scantron_CODEunique();
     my $result;      my $result;
   
       $ssi_error = 0;
   
     # Chunk of form to prompt for a file to grade and how:      # Chunk of form to prompt for a file to grade and how:
   
     $result.= '      $result.= '
Line 5001  sub scantron_selectphase { Line 5285  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 scantron 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 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 5064  sub scantron_selectphase { Line 5377  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 6435  sub scantron_validate_sequence { Line 6748  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 6508  sub scantron_validate_ID { Line 6814  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 6674  ENDSCRIPT Line 6951  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 6694  ENDSCRIPT Line 6971  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 6852  sub prompt_for_corrections { Line 7129  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 7068  sub scantron_validate_CODE { Line 7345  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 7107  sub scantron_validate_doublebubble { Line 7384  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 7155  sub scantron_get_maxbubble { Line 7413  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 $symb = $resource->symb();
   
           my (@parts,@allparts,@possible_parts);
   
         # Need to retrieve part IDs and response IDs because essayresponse,          # Need to retrieve part IDs and response IDs because essayresponse,
         # reactionresponse and organicresponse items are not included in           # reactionresponse and organicresponse items are not included in 
         # $analysis{'parts'} from lonnet::ssi.            # $analysis{'parts'} from lonnet::ssi.  
         my %possible_part_ids;           if (ref($resource->parts()) eq 'ARRAY') {
         if (ref($resource->parts()) eq 'ARRAY') {   
             foreach my $part (@{$resource->parts()}) {              foreach my $part (@{$resource->parts()}) {
                 my @resp_ids = $resource->responseIds($part);                  if (!&Apache::loncommon::check_if_partid_hidden($part,$symb,$udom,$uname)) {
                 foreach my $id (@resp_ids) {                      my @resp_ids = $resource->responseIds($part);
                     $possible_part_ids{$part.'.'.$id} = 1;                      foreach my $id (@resp_ids) {
                           my $part_id = $part.'.'.$id;
                           push(@possible_parts,$part_id);
                       }
                 }                  }
             }              }
         }          }
  my $result=&Apache::lonnet::ssi($resource->src(),  
  ('symb' => $resource->symb()),  
  ('grade_target' => 'analyze'),  
  ('grade_courseid' => $cid),  
  ('grade_domain' => $udom),  
  ('grade_username' => $uname));  
  my (undef, $an) =  
     split(/_HASH_REF__/,$result, 2);  
   
         my @parts;          my $result=&ssi_with_retries($resource->src(), $ssi_retries,
                                           ('symb' => $symb,
                                            'grade_target' => 'analyze',
                                            'grade_courseid' => $cid,
                                            'grade_domain' => $udom,
                                            'grade_username' => $uname));
           my (undef, $an) =
               split(/_HASH_REF__/,$result, 2);
   
  my %analysis = &Apache::lonnet::str2hash($an);   my %analysis = &Apache::lonnet::str2hash($an);
   
         if (ref($analysis{'parts'}) eq 'ARRAY') {          if (ref($analysis{'parts'}) eq 'ARRAY') {
             @parts = @{$analysis{'parts'}};              foreach my $part (@{$analysis{'parts'}}) {
                   my ($id,$respid) = split(/\./,$part);
                   if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) {
                       push(@parts,$part);
                   }
               }
         }          }
         # Add part_ids for any essayresponse items.           # Add part_ids for any essayresponse, reactionresponse or 
         foreach my $part_id (keys(%possible_part_ids)) {          # organicresponse items. 
             if (($analysis{$part_id.'.type'} eq 'essayresponse') ||          foreach my $part_id (@possible_parts) {
                 ($analysis{$part_id.'.type'} eq 'reactionresponse') ||              if (grep(/^\Q$part_id\E$/,@parts)) {
                 ($analysis{$part_id.'.type'} eq 'organicresponse')) {                  push(@allparts,$part_id);
                 if (!grep(/^\Q$part_id\E$/,@parts)) {              } else {
                     push (@parts,$part_id);                  if (($analysis{$part_id.'.type'} eq 'essayresponse') ||
                       ($analysis{$part_id.'.type'} eq 'reactionresponse') ||
                       ($analysis{$part_id.'.type'} eq 'organicresponse')) {
                       push(@allparts,$part_id);
                 }                  }
             }              }
         }          }
   
  foreach my $part_id (@parts) {   foreach my $part_id (@allparts) {
             my $lines = $analysis{"$part_id.bubble_lines"};              my $lines;
   
     # TODO - make this a persistent hash not an array.      # TODO - make this a persistent hash not an array.
   
Line 7222  sub scantron_get_maxbubble { Line 7493  sub scantron_get_maxbubble {
                     $numshown = scalar(@{$analysis{$part_id.'.shown'}});                      $numshown = scalar(@{$analysis{$part_id.'.shown'}});
                 }                  }
                 my $bubbles_per_line = 10;                  my $bubbles_per_line = 10;
                 my $inner_bubble_lines = int($numshown/$bubbles_per_line);                  my $inner_bubble_lines = int($numbub/$bubbles_per_line);
                 if (($numshown % $bubbles_per_line) != 0) {                  if (($numbub % $bubbles_per_line) != 0) {
                     $inner_bubble_lines++;                      $inner_bubble_lines++;
                 }                  }
                 for (my $i=0; $i<$numshown; $i++) {                  for (my $i=0; $i<$numshown; $i++) {
Line 7231  sub scantron_get_maxbubble { Line 7502  sub scantron_get_maxbubble {
                         $inner_bubble_lines.',';                          $inner_bubble_lines.',';
                 }                  }
                 $subdivided_bubble_lines{$response_number} =~ s/,$//;                  $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;              $first_bubble_line{$response_number} = $bubble_line;
Line 7252  sub scantron_get_maxbubble { Line 7526  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) = @_;
Line 7315  sub scantron_validate_missingbubbles { Line 7580  sub scantron_validate_missingbubbles {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
 =pod  
   
 =item scantron_process_students  
   
    Routine that does the actual grading of the bubble sheet information.  
   
    The parsed scanline hash is added to %env   
   
    Then foreach unskipped scanline it does an &Apache::lonnet::ssi()  
    foreach resource , with the form data of  
   
  'submitted'     =>'scantron'   
  'grade_target'  =>'grade',  
  'grade_username'=> username of student  
  'grade_domain'  => domain of student  
  'grade_courseid'=> of course  
  'grade_symb'    => symb of resource to grade  
   
     This triggers a grading pass. The problem grading code takes care  
     of converting the bubbled letter information (now in %env) into a  
     valid submission.  
   
 =cut  
   
 sub scantron_process_students {  sub scantron_process_students {
     my ($r) = @_;      my ($r) = @_;
   
     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});      my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
     my ($symb)=&get_symb($r);      my ($symb)=&get_symb($r);
     if (!$symb) {return '';}      if (!$symb) {
    return '';
       }
     my $default_form_data=&defaultFormData($symb);      my $default_form_data=&defaultFormData($symb);
   
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
Line 7364  SCANTRONFORM Line 7609  SCANTRONFORM
     my @delayqueue;      my @delayqueue;
     my %completedstudents;      my %completedstudents;
           
       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,
Line 7375  SCANTRONFORM Line 7621  SCANTRONFORM
     my ($uname,$udom,$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.
       
   
       # If an ssi failed in scantron_get_maxbubble, put an error message out to
       # the user and return.
   
       if ($ssi_error) {
    $r->print("</form>");
    &ssi_print_error($r);
    $r->print(&show_grading_menu_form($symb));
           &Apache::lonnet::remove_lock($lock);
    return ''; # Dunno why the other returns return '' rather than just returning.
       }
   
     while ($i<$scanlines->{'count'}) {      while ($i<$scanlines->{'count'}) {
   ($uname,$udom)=('','');    ($uname,$udom)=('','');
Line 7402  SCANTRONFORM Line 7660  SCANTRONFORM
   ($uname,$udom)=split(/:/,$uname);    ($uname,$udom)=split(/:/,$uname);
   
  &Apache::lonxml::clear_problem_counter();   &Apache::lonxml::clear_problem_counter();
   &Apache::lonnet::appenv(%$scan_record);    &Apache::lonnet::appenv($scan_record);
   
  if (&scantron_clear_skip($scanlines,$scan_data,$i)) {   if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
     &scantron_putfile($scanlines,$scan_data);      &scantron_putfile($scanlines,$scan_data);
Line 7423  SCANTRONFORM Line 7681  SCANTRONFORM
  $form{'CODE'}=$scan_record->{'scantron.CODE'};   $form{'CODE'}=$scan_record->{'scantron.CODE'};
     } else {      } else {
  $form{'CODE'}='';   $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 $result=&Apache::lonnet::ssi($resource->src(),%form);  
     if ($result ne '') {  
     }  
     if (&Apache::loncommon::connection_aborted($r)) { last; }      if (&Apache::loncommon::connection_aborted($r)) { last; }
  }   }
  $completedstudents{$uname}={'line'=>$line};   $completedstudents{$uname}={'line'=>$line};
Line 7436  SCANTRONFORM Line 7701  SCANTRONFORM
  &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 7444  SCANTRONFORM Line 7710  SCANTRONFORM
     return '';      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 7492  sub scantron_upload_scantron_data { Line 7750  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 7561  sub scantron_upload_scantron_data_save { Line 7811  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 7577  sub valid_file { Line 7819  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 7626  sub scantron_download_scantron_data { Line 7858  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 = (
                       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 $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,undef,1,0);
       my (%scandata,%lastname,%bylast);
       $r->print('
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");
   
 =back      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,$uname,$started);
   
       &Apache::grades::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;
           my (%expected,%startpos);
           foreach my $resource (@resources) {
               next if (!$resource->is_problem());
               my $symb = $resource->symb();
               my $partsref = $resource->parts();
               my @parts;
               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);
       $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 scantron 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 scantron 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 scantron 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 scantron grading pass.').'<br />'.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original scantron sheets.');  
       }
       $r->print('</form><br />'.$grading_menu_button);
       return;
   }
   
 =cut  
   
 #-------- end of section for handling grading scantron forms -------  #-------- end of section for handling grading scantron forms -------
 #  #
Line 7685  sub grading_menu { Line 8177  sub grading_menu {
                  });                   });
     $fields{'command'} = 'csvform';      $fields{'command'} = 'csvform';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,      push(@menu, { url => $url,
                    name => &mt('Upload Scores'),                     name => &mt('Upload Scores'),
                    short_description =>                      short_description => 
             &mt('Specify a file containing the class scores for current resource.')});              &mt('Specify a file containing the class scores for current resource.')});
     $fields{'command'} = 'processclicker';      $fields{'command'} = 'processclicker';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,      push(@menu, { url => $url,
                    name => &mt('Process Clicker'),                     name => &mt('Process Clicker'),
                    short_description =>                      short_description => 
             &mt('Specify a file containing the clicker information for this resource.')});              &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);      $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,      push(@menu, { url => $url,
                    name => &mt('Grade/Manage Scantron Forms'),                     name => &mt('Grade/Manage/Review Scantron Forms'),
                    short_description =>                      short_description => 
             &mt('')});              &mt('Grade scantron exams, upload/download scantron data files, and review previously graded scantron exams.')});
     $fields{'command'} = 'verify';      $fields{'command'} = 'verify';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => "",      push(@menu, { url => "",
                    name => &mt('Verify Receipt'),                     name => &mt('Verify Receipt'),
                    short_description =>                      short_description => 
             &mt('')});              &mt('')});
Line 7859  GRADINGMENUJS Line 8351  GRADINGMENUJS
              <div class="LC_grade_select_mode_selector_body">               <div class="LC_grade_select_mode_selector_body">
        <select name="section" multiple="multiple" size="5">'."\n";         <select name="section" multiple="multiple" size="5">'."\n";
     if (ref($sections)) {      if (ref($sections)) {
  foreach my $section (sort (@$sections)) {   foreach my $section (sort(@$sections)) {
     $result.='<option value="'.$section.'" '.      $result.='<option value="'.$section.'" '.
  ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";   ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";
  }   }
Line 8050  sub process_clicker { Line 8542  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 8061  sub process_clicker { Line 8553  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 8118  function sanitycheck() { Line 8612  function sanitycheck() {
 <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 8144  sub process_clicker_file { Line 8641  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 8162  sub process_clicker_file { Line 8672  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 8207  sub process_clicker_file { Line 8719  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 8222  ENDHEADER Line 8737  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 8265  ENDHEADER Line 8784  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 8336  sub interwrite_eval { Line 8855  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 8410  ENDHEADER Line 8929  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 8423  ENDHEADER Line 8947  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 8466  sub handler { Line 8990  sub handler {
  &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));   &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
     }      }
   
       $ssi_error = 0;
     $request->print(&Apache::loncommon::start_page('Grading'));      $request->print(&Apache::loncommon::start_page('Grading'));
     if ($symb eq '' && $command eq '') {      if ($symb eq '' && $command eq '') {
  if ($env{'user.adv'}) {   if ($env{'user.adv'}) {
Line 8479  sub handler { Line 9003  sub handler {
  if ($tsymb) {   if ($tsymb) {
     my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);      my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
     if (&Apache::lonnet::allowed('mgr',$tcrsid)) {      if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
  $request->print(&Apache::lonnet::ssi_body('/res/'.$url,   $request->print(&ssi_with_retries('/res/'.$url, $ssi_retries,
   ('grade_username' => $tuname,    ('grade_username' => $tuname,
    'grade_domain' => $tudom,     'grade_domain' => $tudom,
    'grade_courseid' => $tcrsid,     'grade_courseid' => $tcrsid,
Line 8562  sub handler { Line 9086  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("Access Denied ($command)");
  }   }
     }      }
       if ($ssi_error) {
    &ssi_print_error($request);
       }
     $request->print(&Apache::loncommon::end_page());      $request->print(&Apache::loncommon::end_page());
     &reset_caches();      &reset_caches();
     return '';      return '';

Removed from v.1.512  
changed lines
  Added in v.1.529


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