Diff for /loncom/homework/grades.pm between versions 1.418 and 1.459

version 1.418, 2007/06/22 23:45:19 version 1.459, 2007/10/15 09:47:47
Line 35  use Apache::loncommon; Line 35  use Apache::loncommon;
 use Apache::lonhtmlcommon;  use Apache::lonhtmlcommon;
 use Apache::lonnavmaps;  use Apache::lonnavmaps;
 use Apache::lonhomework;  use Apache::lonhomework;
   use Apache::lonpickcode;
 use Apache::loncoursedata;  use Apache::loncoursedata;
 use Apache::lonmsg();  use Apache::lonmsg();
 use Apache::Constants qw(:common);  use Apache::Constants qw(:common);
Line 45  use LONCAPA; Line 46  use LONCAPA;
   
 use POSIX qw(floor);  use POSIX qw(floor);
   
 my %oldessays=();  
 my %perm=();  my %perm=();
   my %bubble_lines_per_response = ();     # no. bubble lines for each response.
                                      # index is "symb.part_id"
   
   my %first_bubble_line = (); # First bubble line no. for each bubble.
   
   # Save and restore the bubble lines array to the form env.
   
   
   sub save_bubble_lines {
       &Apache::lonnet::logthis("Saving bubble_lines...");
       foreach my $line (keys(%bubble_lines_per_response)) {
    &Apache::lonnet::logthis("Saving form.scantron.bubblelines.$line value: $bubble_lines_per_response{$line}");
    $env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
    $env{"form.scantron.first_bubble_line.$line"} =
       $first_bubble_line{$line};
       }
   }
   
   
   sub restore_bubble_lines {
       my $line = 0;
       %bubble_lines_per_response = ();
       while ($env{"form.scantron.bubblelines.$line"}) {
    my $value = $env{"form.scantron.bubblelines.$line"};
    &Apache::lonnet::logthis("Restoring form.scantron.bubblelines.$line value: $value");
    $bubble_lines_per_response{$line} = $value;
    $first_bubble_line{$line}  =
       $env{"form.scantron.first_bubble_line.$line"};
    $line++;
       }
   
   }
   
   #  Given the parsed scanline, get the response for 
   #  'answer' number n:
   
   sub get_response_bubbles {
       my ($parsed_line, $response)  = @_;
   
       my $bubble_line = $first_bubble_line{$response};
       my $bubble_lines= $bubble_lines_per_response{$response};
       my $selected = "";
   
       for (my $bline = 0; $bline < $bubble_lines; $bline++) {
    $selected .= $$parsed_line{"scantron.$bubble_line.answer"};
    $bubble_line++;
       }
       return $selected;
   }
   
   
 # ----- These first few routines are general use routines.----  # ----- These first few routines are general use routines.----
   
   # Return the number of occurences of a pattern in a string.
   
   sub occurence_count {
       my ($string, $pattern) = @_;
   
       my @matches = ($string =~ /$pattern/g);
   
       return scalar(@matches);
   }
   
   
   # Take a string known to have digits and convert all the
   # digits into letters in the range J,A..I.
   
   sub digits_to_letters {
       my ($input) = @_;
   
       my @alphabet = ('J', 'A'..'I');
   
       my @input    = split(//, $input);
       my $output ='';
       for (my $i = 0; $i < scalar(@input); $i++) {
    if ($input[$i] =~ /\d/) {
       $output .= $alphabet[$input[$i]];
    } else {
       $output .= $input[$i];
    }
       }
       return $output;
   }
   
 #  #
 # --- Retrieve the parts from the metadata file.---  # --- Retrieve the parts from the metadata file.---
 sub getpartlist {  sub getpartlist {
     my ($symb) = @_;      my ($symb) = @_;
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);  
     my $partorder = &Apache::lonnet::metadata($url, 'partorder');      my $navmap   = Apache::lonnavmaps::navmap->new();
     my @parts;      my $res      = $navmap->getBySymb($symb);
     if ($partorder) {      my $partlist = $res->parts();
  for my $part (split (/,/,$partorder)) {      my $url      = $res->src();
     if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) {      my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
  push(@parts, $part);  
     }  
  }      
     } else {  
  my $metadata = &Apache::lonnet::metadata($url, 'packages');  
  foreach (split(/\,/,$metadata)) {  
     if ($_ =~ /^part_(.*)$/) {  
  if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) {  
     push(@parts, $1);  
  }  
     }  
  }  
     }  
     my @stores;      my @stores;
     foreach my $part (@parts) {      foreach my $part (@{ $partlist }) {
  my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));  
  foreach my $key (@metakeys) {   foreach my $key (@metakeys) {
     if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }      if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
  }   }
Line 195  sub showResourceInfo { Line 265  sub showResourceInfo {
     return $result,$responseType,$hdgrade,$partlist,$handgrade;      return $result,$responseType,$hdgrade,$partlist,$handgrade;
 }  }
   
   sub reset_caches {
       &reset_analyze_cache();
       &reset_perm();
   }
   
 sub get_order {  {
     my ($partid,$respid,$symb,$uname,$udom)=@_;      my %analyze_cache;
     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);  
     $url=&Apache::lonnet::clutter($url);      sub reset_analyze_cache {
     my $subresult=&Apache::lonnet::ssi($url,   undef(%analyze_cache);
        ('grade_target' => 'analyze'),      }
        ('grade_domain' => $udom),  
        ('grade_symb' => $symb),      sub get_analyze {
        ('grade_courseid' =>    my ($symb,$uname,$udom)=@_;
         $env{'request.course.id'}),   my $key = "$symb\0$uname\0$udom";
        ('grade_username' => $uname));   return $analyze_cache{$key} if (exists($analyze_cache{$key}));
     (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);  
     my %analyze=&Apache::lonnet::str2hash($subresult);   my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
     return ($analyze{"$partid.$respid.shown"});   $url=&Apache::lonnet::clutter($url);
    my $subresult=&Apache::lonnet::ssi($url,
      ('grade_target' => 'analyze'),
      ('grade_domain' => $udom),
      ('grade_symb' => $symb),
      ('grade_courseid' => 
       $env{'request.course.id'}),
      ('grade_username' => $uname));
    (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
    my %analyze=&Apache::lonnet::str2hash($subresult);
    return $analyze_cache{$key} = \%analyze;
       }
   
       sub get_order {
    my ($partid,$respid,$symb,$uname,$udom)=@_;
    my $analyze = &get_analyze($symb,$uname,$udom);
    return $analyze->{"$partid.$respid.shown"};
       }
   
       sub get_radiobutton_correct_foil {
    my ($partid,$respid,$symb,$uname,$udom)=@_;
    my $analyze = &get_analyze($symb,$uname,$udom);
    foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) {
       if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
    return $foil;
       }
    }
       }
 }  }
   
 #--- Clean response type for display  #--- Clean response type for display
 #--- Currently filters option/rank/radiobutton/match/essay/Task  #--- Currently filters option/rank/radiobutton/match/essay/Task
 #        response types only.  #        response types only.
Line 259  sub cleanRecord { Line 361  sub cleanRecord {
     } elsif ($response eq 'radiobutton') {      } elsif ($response eq 'radiobutton') {
  my %answer=&Apache::lonnet::str2hash($answer);   my %answer=&Apache::lonnet::str2hash($answer);
  my ($toprow,$bottomrow);   my ($toprow,$bottomrow);
  my $correct=($order->[0])+1;   my $correct = 
  for (my $i=1;$i<=$#$order;$i++) {      &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);
     my $foil=$order->[$i];   foreach my $foil (@$order) {
     if (exists($answer{$foil})) {      if (exists($answer{$foil})) {
  if ($i == $correct) {   if ($foil eq $correct) {
     $toprow.='<td><b>true</b></td>';      $toprow.='<td><b>true</b></td>';
  } else {   } else {
     $toprow.='<td><i>true</i></td>';      $toprow.='<td><i>true</i></td>';
Line 327  sub cleanRecord { Line 429  sub cleanRecord {
     $result.='</ul>';      $result.='</ul>';
     return $result;      return $result;
  }   }
              } elsif ( $response =~ m/(?:numerical|formula)/) {
    $answer = 
       &Apache::loncommon::format_previous_attempt_value('submission',
         $answer);
     }      }
     return $answer;      return $answer;
 }  }
Line 371  COMMONJSFUNCTIONS Line 476  COMMONJSFUNCTIONS
 #--- Dumps the class list with usernames,list of sections,  #--- Dumps the class list with usernames,list of sections,
 #--- section, ids and fullnames for each user.  #--- section, ids and fullnames for each user.
 sub getclasslist {  sub getclasslist {
     my ($getsec,$filterlist) = @_;      my ($getsec,$filterlist,$getgroup) = @_;
     my @getsec;      my @getsec;
       my @getgroup;
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     if (!ref($getsec)) {      if (!ref($getsec)) {
  if ($getsec ne '' && $getsec ne 'all') {   if ($getsec ne '' && $getsec ne 'all') {
     @getsec=($getsec);      @getsec=($getsec);
Line 381  sub getclasslist { Line 488  sub getclasslist {
  @getsec=@{$getsec};   @getsec=@{$getsec};
     }      }
     if (grep(/^all$/,@getsec)) { undef(@getsec); }      if (grep(/^all$/,@getsec)) { undef(@getsec); }
       if (!ref($getgroup)) {
    if ($getgroup ne '' && $getgroup ne 'all') {
       @getgroup=($getgroup);
    }
       } else {
    @getgroup=@{$getgroup};
       }
       if (grep(/^all$/,@getgroup)) { undef(@getgroup); }
   
     my $classlist=&Apache::loncoursedata::get_classlist();      my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist();
     # Bail out if we were unable to get the classlist      # Bail out if we were unable to get the classlist
     return if (! defined($classlist));      return if (! defined($classlist));
       &Apache::loncoursedata::get_group_memberships($classlist,$keylist);
     #      #
     my %sections;      my %sections;
     my %fullnames;      my %fullnames;
Line 401  sub getclasslist { Line 517  sub getclasslist {
             $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];              $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
         my $status   =           my $status   = 
             $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];              $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
           my $group   = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  # filter students according to status selected   # filter students according to status selected
  if ($filterlist && $env{'form.Status'} ne 'Any') {   if ($filterlist && (!($stu_status =~ /Any/))) {
     if ($env{'form.Status'} ne $status) {      if (!($stu_status =~ $status)) {
  delete ($classlist->{$student});   delete($classlist->{$student});
  next;   next;
     }      }
  }   }
    # filter students according to groups selected
    my @stu_groups = split(/,/,$group);
    if (@getgroup) {
       my $exclude = 1;
       foreach my $grp (@getgroup) {
           foreach my $stu_group (@stu_groups) {
               if ($stu_group eq $grp) {
                   $exclude = 0;
                  } 
           }
              if (($grp eq 'none') && !$group) {
                  $exclude = 0;
           }
       }
       if ($exclude) {
           delete($classlist->{$student});
       }
    }
  $section = ($section ne '' ? $section : 'none');   $section = ($section ne '' ? $section : 'none');
  if (&canview($section)) {   if (&canview($section)) {
     if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {      if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
  $sections{$section}++;   $sections{$section}++;
  $fullnames{$student}=$fullname;   if ($classlist->{$student}) {
       $fullnames{$student}=$fullname;
    }
     } else {      } else {
  delete($classlist->{$student});   delete($classlist->{$student});
     }      }
Line 485  sub student_gradeStatus { Line 623  sub student_gradeStatus {
 # Shows a student's view of problem and submission  # Shows a student's view of problem and submission
 sub jscriptNform {  sub jscriptNform {
     my ($symb) = @_;      my ($symb) = @_;
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     my $jscript='<script type="text/javascript" language="javascript">'."\n".      my $jscript='<script type="text/javascript" language="javascript">'."\n".
  '    function viewOneStudent(user,domain) {'."\n".   '    function viewOneStudent(user,domain) {'."\n".
  ' document.onestudent.student.value = user;'."\n".   ' document.onestudent.student.value = user;'."\n".
Line 496  sub jscriptNform { Line 635  sub jscriptNform {
  '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".   '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".   '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
  '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".   '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
  '<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".   '<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
  '<input type="hidden" name="command" value="submission" />'."\n".   '<input type="hidden" name="command" value="submission" />'."\n".
  '<input type="hidden" name="student" value="" />'."\n".   '<input type="hidden" name="student" value="" />'."\n".
  '<input type="hidden" name="userdom" value="" />'."\n".   '<input type="hidden" name="userdom" value="" />'."\n".
Line 504  sub jscriptNform { Line 643  sub jscriptNform {
     return $jscript;      return $jscript;
 }  }
   
   
   
 # Given the score (as a number [0-1] and the weight) what is the final  # Given the score (as a number [0-1] and the weight) what is the final
 # point value? This function will round to the nearest tenth, third,  # point value? This function will round to the nearest tenth, third,
 # or quarter if one of those is within the tolerance of .00001.  # or quarter if one of those is within the tolerance of .00001.
Line 538  sub compute_points { Line 679  sub compute_points {
 #  #
   
 sub most_similar {  sub most_similar {
     my ($uname,$udom,$uessay)=@_;      my ($uname,$udom,$uessay,$old_essays)=@_;
   
 # ignore spaces and punctuation  # ignore spaces and punctuation
   
Line 555  sub most_similar { Line 696  sub most_similar {
     my $scrsid='';      my $scrsid='';
     my $sessay='';      my $sessay='';
 # go through all essays ...  # go through all essays ...
     foreach my $tkey (keys %oldessays) {      foreach my $tkey (keys(%$old_essays)) {
  my ($tname,$tdom,$tcrsid)=split(/\./,$tkey);   my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
 # ... except the same student  # ... except the same student
         if (($tname ne $uname) || ($tdom ne $udom)) {          next if (($tname eq $uname) && ($tdom eq $udom));
     my $tessay=$oldessays{$tkey};   my $tessay=$old_essays->{$tkey};
             $tessay=~s/\W+/ /gs;   $tessay=~s/\W+/ /gs;
 # String similarity gives up if not even limit  # String similarity gives up if not even limit
             my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);   my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
 # Found one  # Found one
             if ($tsimilar>$limit) {   if ($tsimilar>$limit) {
  $limit=$tsimilar;      $limit=$tsimilar;
                 $sname=$tname;      $sname=$tname;
                 $sdom=$tdom;      $sdom=$tdom;
                 $scrsid=$tcrsid;      $scrsid=$tcrsid;
                 $sessay=$oldessays{$tkey};      $sessay=$old_essays->{$tkey};
             }   }
         }   
     }      }
     if ($limit>0.6) {      if ($limit>0.6) {
        return ($sname,$sdom,$scrsid,$sessay,$limit);         return ($sname,$sdom,$scrsid,$sessay,$limit);
Line 661  sub listStudents { Line 801  sub listStudents {
     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};      my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
     my $cnum      = $env{"course.$env{'request.course.id'}.num"};      my $cnum      = $env{"course.$env{'request.course.id'}.num"};
     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};      my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
       my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
     my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};      my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
   
     my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';      my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
     $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ?       $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
  &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};   &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
Line 722  LISTJAVASCRIPT Line 862  LISTJAVASCRIPT
     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {      if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
  $gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n";   $gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n";
     }      }
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'};      my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
     $env{'form.Status'} = $saveStatus;      $env{'form.Status'} = $saveStatus;
   
     $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n".      $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n".
  '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n".   '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n".
  '<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n".   '<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n".
Line 736  LISTJAVASCRIPT Line 875  LISTJAVASCRIPT
         '<option value=".25">Quarter Points</option>'.          '<option value=".25">Quarter Points</option>'.
         '<option value=".1">Tenths of a Point</option>'.          '<option value=".1">Tenths of a Point</option>'.
         '</select>'.          '</select>'.
           &build_section_inputs().
  '<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".  
  '<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".   '<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
  '<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".   '<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
  '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".   '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
Line 747  LISTJAVASCRIPT Line 885  LISTJAVASCRIPT
  '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";   '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
   
     if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {      if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
  $gradeTable.='<input type="hidden" name="Status"   value="'.$env{'form.Status'}.'" />'."\n";   $gradeTable.='<input type="hidden" name="Status"   value="'.$stu_status.'" />'."\n";
     } else {      } else {
  $gradeTable.='<b>Student Status:</b> '.   $gradeTable.='<b>Student Status:</b> '.
     &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';      &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';
Line 764  LISTJAVASCRIPT Line 902  LISTJAVASCRIPT
  'value="Next->" /> <br />'."\n";   'value="Next->" /> <br />'."\n";
     $gradeTable.=&check_buttons();      $gradeTable.=&check_buttons();
     $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />Check For Plagiarism</label>';      $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />Check For Plagiarism</label>';
     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1');      my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
     $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.      $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.
  '<table border="0"><tr bgcolor="#e6ffff">';   '<table border="0"><tr bgcolor="#e6ffff">';
     my $loop = 0;      my $loop = 0;
Line 839  LISTJAVASCRIPT Line 977  LISTJAVASCRIPT
   
  $ctr++;   $ctr++;
  my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];   my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
           my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  if ( $perm{'vgr'} eq 'F' ) {   if ( $perm{'vgr'} eq 'F' ) {
     $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);      $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);
     $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.      $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
Line 847  LISTJAVASCRIPT Line 985  LISTJAVASCRIPT
                $student.':'.$$fullname{$student}.':::SECTION'.$section.                 $student.':'.$$fullname{$student}.':::SECTION'.$section.
        ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.         ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
        &nameUserString(undef,$$fullname{$student},$uname,$udom).         &nameUserString(undef,$$fullname{$student},$uname,$udom).
        '&nbsp;'.$section.'</td>'."\n";         '&nbsp;'.$section.'/'.$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)) {
Line 1663  sub download_all_link { Line 1801  sub download_all_link {
     return      return
 }  }
   
   sub build_section_inputs {
       my $section_inputs;
       if ($env{'form.section'} eq '') {
           $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
       } else {
           my @sections = &Apache::loncommon::get_env_multiple('form.section');
           foreach my $section (@sections) {
               $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
           }
       }
       return $section_inputs;
   }
   
 # --------------------------- show submissions of a student, option to grade   # --------------------------- show submissions of a student, option to grade 
 sub submission {  sub submission {
     my ($request,$counter,$total) = @_;      my ($request,$counter,$total) = @_;
   
     my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});      my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
     $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?      $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});      my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
     $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';      $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
   
     my $symb = &get_symb($request);       my $symb = &get_symb($request); 
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }      if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
   
Line 1691  sub submission { Line 1840  sub submission {
  '" src="'.$request->dir_config('lonIconsURL').   '" src="'.$request->dir_config('lonIconsURL').
  '/check.gif" height="16" border="0" />';   '/check.gif" height="16" border="0" />';
   
       my %old_essays;
     # header info      # header info
     if ($counter == 0) {      if ($counter == 0) {
  &sub_page_js($request);   &sub_page_js($request);
Line 1723  sub submission { Line 1873  sub submission {
     &Apache::lonxml::clear_problem_counter();      &Apache::lonxml::clear_problem_counter();
     $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));      $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
  }   }
   
  # kwclr is the only variable that is guaranteed to be non blank    # kwclr is the only variable that is guaranteed to be non blank 
         # if this subroutine has been called once.          # if this subroutine has been called once.
  my %keyhash = ();   my %keyhash = ();
Line 1742  sub submission { Line 1892  sub submission {
     $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';      $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
  }   }
  my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};   my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
    my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
  $request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".   $request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
  '<input type="hidden" name="command"    value="handgrade" />'."\n".   '<input type="hidden" name="command"    value="handgrade" />'."\n".
  '<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".   '<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
  '<input type="hidden" name="Status"     value="'.$env{'form.Status'}.'" />'."\n".   '<input type="hidden" name="Status"     value="'.$stu_status.'" />'."\n".
  '<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".   '<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
  '<input type="hidden" name="probTitle"  value="'.$env{'form.probTitle'}.'" />'."\n".   '<input type="hidden" name="probTitle"  value="'.$env{'form.probTitle'}.'" />'."\n".
  '<input type="hidden" name="refresh"    value="off" />'."\n".   '<input type="hidden" name="refresh"    value="off" />'."\n".
Line 1757  sub submission { Line 1907  sub submission {
  '<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".   '<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
  '<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".   '<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
  '<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".   '<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
  '<input type="hidden" name="section"    value="'.$env{'form.section'}.'" />'."\n".   &build_section_inputs().
  '<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".   '<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
  '<input type="hidden" name="handgrade"  value="'.$env{'form.handgrade'}.'" />'."\n".   '<input type="hidden" name="handgrade"  value="'.$env{'form.handgrade'}.'" />'."\n".
  '<input type="hidden" name="NCT"'.   '<input type="hidden" name="NCT"'.
Line 1805  KEYWORDS Line 1955  KEYWORDS
     my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);      my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
     $apath=&escape($apath);      $apath=&escape($apath);
     $apath=~s/\W/\_/gs;      $apath=~s/\W/\_/gs;
     %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);      %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
         }          }
     }      }
   
   # This is where output for one specific student would start
       my $bgcolor='#DDEEDD';
       if (int($counter/2) eq $counter) { $bgcolor='#DDDDEE'; }
       $request->print("\n\n".
                       '<p><table border="2"><tr><th bgcolor="'.$bgcolor.'">'.$env{'form.fullname'}.'</th></tr><tr><td bgcolor="'.$bgcolor.'">');
   
     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {      if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
  $request->print('<br /><br /><br />') if ($counter > 0);  
  my $mode;   my $mode;
  if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {   if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
     $mode='both';      $mode='both';
Line 1943  KEYWORDS Line 2098  KEYWORDS
     my $similar='';      my $similar='';
     if($env{'form.checkPlag'}){      if($env{'form.checkPlag'}){
  my ($oname,$odom,$ocrsid,$oessay,$osim)=   my ($oname,$odom,$ocrsid,$oessay,$osim)=
     &most_similar($uname,$udom,$subval);      &most_similar($uname,$udom,$subval,\%old_essays);
  if ($osim) {   if ($osim) {
     $osim=int($osim*100.0);      $osim=int($osim*100.0);
     $similar="<hr /><h3><span class=\"LC_warning\">Essay".      my %old_course_desc = 
  " is $osim% similar to an essay by ".   &Apache::lonnet::coursedescription($ocrsid,
  &Apache::loncommon::plainname($oname,$odom).     {'one_time' => 1});
   
       $similar="<hr /><h3><span class=\"LC_warning\">".
    &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])',
       $osim,
       &Apache::loncommon::plainname($oname,$odom),
       $oname,$odom,
       $old_course_desc{'description'},
       $old_course_desc{'num'},
       $old_course_desc{'domain'}).
  '</span></h3><blockquote><i>'.   '</span></h3><blockquote><i>'.
  &keywords_highlight($oessay).   &keywords_highlight($oessay).
  '</i></blockquote><hr />';   '</i></blockquote><hr />';
Line 2071  KEYWORDS Line 2235  KEYWORDS
     }      }
     $request->print($result.'</td></tr></table></td></tr></table>'."\n");      $request->print($result.'</td></tr></table></td></tr></table>'."\n");
   
   # Done with printing info for one student
   
       $request->print('</td></tr></table></p>');
   
   
     # print end of form      # print end of form
     if ($counter == $total) {      if ($counter == $total) {
  my $endform='<table border="0"><tr><td>'."\n";   my $endform='<table border="0"><tr><td>'."\n";
Line 2733  sub version_selected_portfile { Line 2902  sub version_selected_portfile {
     my $new_answer;      my $new_answer;
     $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");      $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
     if($env{'form.copy'} eq '-1') {      if($env{'form.copy'} eq '-1') {
         &Apache::lonnet::logthis('problem getting file '.$file_name);  
         $new_answer = 'problem getting file';          $new_answer = 'problem getting file';
     } else {      } else {
         $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;          $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
Line 2954  sub viewgrades { Line 3122  sub viewgrades {
     $result.=&jscriptNform($symb);      $result.=&jscriptNform($symb);
   
     #beginning of class grading form      #beginning of class grading form
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".      $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\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="command" value="editgrades" />'."\n".   '<input type="hidden" name="command" value="editgrades" />'."\n".
  '<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n".   &build_section_inputs().
  '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".   '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
  '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n".   '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
  '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";   '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
   
     my $sectionClass;      my $sectionClass;
       my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     if ($env{'form.section'} eq 'all') {      if ($env{'form.section'} eq 'all') {
  $sectionClass='Class </h3>';   $sectionClass='Class </h3>';
     } elsif ($env{'form.section'} eq 'none') {      } elsif ($env{'form.section'} eq 'none') {
  $sectionClass='Students in no Section </h3>';   $sectionClass=&mt('Students in no Section').'</h3>';
     } else {      } else {
  $sectionClass='Students in Section '.$env{'form.section'}.'</h3>';   $sectionClass=&mt('Students in Section(s) [_1]',$section_display).'</h3>';
     }      }
     $result.='<h3>Assign Common Grade To '.$sectionClass;      $result.='<h3>'.&mt('Assign Common Grade To [_1]',$sectionClass);
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".      $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
  '<table border=0><tr bgcolor="#ffffdd"><td>';   '<table border=0><tr bgcolor="#ffffdd"><td>';
     #radio buttons/text box for assigning points for a section or class.      #radio buttons/text box for assigning points for a section or class.
Line 3076  sub viewgrades { Line 3246  sub viewgrades {
  'onClick="javascript:submit();" target="_self" /></form>'."\n";   'onClick="javascript:submit();" target="_self" /></form>'."\n";
     if (scalar(%$fullname) eq 0) {      if (scalar(%$fullname) eq 0) {
  my $colspan=3+scalar(@parts);   my $colspan=3+scalar(@parts);
  $result='<span class="LC_warning">There are no students in section "'.$env{'form.section'}.   my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     '" with enrollment status "'.$env{'form.Status'}.'" to modify or grade.</span>';          my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
    $result='<span class="LC_warning">'.
       &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade',
           $section_display, $stu_status).
       '</span>';
     }      }
     $result.=&show_grading_menu_form($symb);      $result.=&show_grading_menu_form($symb);
     return $result;      return $result;
Line 3154  sub editgrades { Line 3328  sub editgrades {
     my ($request) = @_;      my ($request) = @_;
   
     my $symb=&get_symb($request);      my $symb=&get_symb($request);
     my $title='<h3><span class="LC_info">Current Grade Status</span></h3>';      my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     $title.='<h4><b>Current Resource: </b>'.$env{'form.probTitle'}.'</h4><br />'."\n";      my $title='<h3><span class="LC_info">'.&mt('Current Grade Status').'</span></h3>';
     $title.='<h4><b>Section: </b>'.$env{'form.section'}.'</h4>'."\n";      $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4><br />'."\n";
       $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
   
     my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";      my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";
     $result.= '<table border="0"><tr bgcolor="#deffff">'.      $result.= '<table border="0"><tr bgcolor="#deffff">'.
Line 3354  sub split_part_type { Line 3529  sub split_part_type {
     my ($partstr) = @_;      my ($partstr) = @_;
     my ($temp,@allparts)=split(/_/,$partstr);      my ($temp,@allparts)=split(/_/,$partstr);
     my $type=pop(@allparts);      my $type=pop(@allparts);
     my $part=join('.',@allparts);      my $part=join('_',@allparts);
     return ($part,$type);      return ($part,$type);
 }  }
   
Line 3760  sub csvuploadassign { Line 3935  sub csvuploadassign {
  }   }
  if (! %grades) { push(@skipped,"$username:$domain no data to save"); }   if (! %grades) { push(@skipped,"$username:$domain no data to save"); }
  $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";   $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
 # &Apache::lonnet::logthis(" storing ".(join('-',%grades)));  
  my $result=&Apache::lonnet::cstore(\%grades,$symb,   my $result=&Apache::lonnet::cstore(\%grades,$symb,
    $env{'request.course.id'},     $env{'request.course.id'},
    $domain,$username);     $domain,$username);
Line 3827  LISTJAVASCRIPT Line 4001  LISTJAVASCRIPT
   
     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";      $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
     $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";      $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";
     my ($titles,$symbx) = &getSymbMap($request);      my ($titles,$symbx) = &getSymbMap();
     my ($curpage) =&Apache::lonnet::decode_symb($symb);       my ($curpage) =&Apache::lonnet::decode_symb($symb); 
 #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb);   #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
 #    my $type=($curpage =~ /\.(page|sequence)/);  #    my $type=($curpage =~ /\.(page|sequence)/);
Line 3857  LISTJAVASCRIPT Line 4031  LISTJAVASCRIPT
  '<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n".   '<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n".
  '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> by dates and submissions</label>'."\n".   '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> by dates and submissions</label>'."\n".
  '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n";   '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n";
       
     $result.='<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".      $result.=&build_section_inputs();
  '<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
       $result.='<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
  '<input type="hidden" name="command" value="displayPage" />'."\n".   '<input type="hidden" name="command" value="displayPage" />'."\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="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";   '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
Line 3909  LISTJAVASCRIPT Line 4084  LISTJAVASCRIPT
 }  }
   
 sub getSymbMap {  sub getSymbMap {
     my ($request) = @_;  
     my $navmap = Apache::lonnavmaps::navmap->new();      my $navmap = Apache::lonnavmaps::navmap->new();
   
     my %symbx = ();      my %symbx = ();
Line 4113  sub displaySubByDates { Line 4287  sub displaySubByDates {
   
  my $where = ($isTask ? "$version:resource.$interaction"   my $where = ($isTask ? "$version:resource.$interaction"
              : "$version:resource");               : "$version:resource");
  #&Apache::lonnet::logthis(" got $where");  
  $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';   $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';
  if ($isCODE) {   if ($isCODE) {
     $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';      $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
Line 4133  sub displaySubByDates { Line 4306  sub displaySubByDates {
   
     my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)      my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
                : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));                 : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
     #&Apache::lonnet::logthis("match $matchKey $responseId (".$$record{$version.':'.$matchKey});  
     $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';      $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';
     $displaySub[0].='<span class="LC_internal_info">(ID&nbsp;'.      $displaySub[0].='<span class="LC_internal_info">(ID&nbsp;'.
  $responseId.')</span>&nbsp;<b>';   $responseId.')</span>&nbsp;<b>';
Line 4354  sub updateGradeByPage { Line 4526  sub updateGradeByPage {
 #  #
 #------ start of section for handling grading by page/sequence ---------  #------ start of section for handling grading by page/sequence ---------
   
   =pod
   
   =head1 Bubble sheet grading routines
   
     For this documentation:
   
      'scanline' refers to the full line of characters
      from the file that we are parsing that represents one entire sheet
   
      'bubble line' refers to the data
      representing the line of bubbles that are on the physical bubble sheet
   
   
   The overall process is that a scanned in bubble sheet data is uploaded
   into a course. When a user wants to grade, they select a
   sequence/folder of resources, a file of bubble sheet info, and pick
   one of the predefined configurations for what each scanline looks
   like.
   
   Next each scanline is checked for any errors of either 'missing
   bubbles' (it's an error because it may have been mis-scanned
   because too light bubbling), 'double bubble' (each bubble line should
   have no more that one letter picked), invalid or duplicated CODE,
   invalid student ID
   
   If the CODE option is used that determines the randomization of the
   homework problems, either way the student ID is looked up into a
   username:domain.
   
   During the validation phase the instructor can choose to skip scanlines. 
   
   After the validation phase, there are now 3 bubble sheet files
   
     scantron_original_filename (unmodified original file)
     scantron_corrected_filename (file where the corrected information has replaced the original information)
     scantron_skipped_filename (contains the exact text of scanlines that where skipped)
   
   Also there is a separate hash nohist_scantrondata that contains extra
   correction information that isn't representable in the bubble sheet
   file (see &scantron_getfile() for more information)
   
   After all scanlines are either valid, marked as valid or skipped, then
   foreach line foreach problem in the picked sequence, an ssi request is
   made that simulates a user submitting their selected letter(s) against
   the homework problem.
   
   =over 4
   
   
   
   =item defaultFormData
   
     Returns html hidden inputs used to hold context/default values.
   
    Arguments:
     $symb - $symb of the current resource 
   
   =cut
   
 sub defaultFormData {  sub defaultFormData {
     my ($symb)=@_;      my ($symb)=@_;
     return '      return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
       <input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".  
      '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".       '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
      '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";       '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 }  }
   
   
   =pod 
   
   =item getSequenceDropDown
   
      Return html dropdown of possible sequences to grade
    
    Arguments:
      $symb - $symb of the current resource 
   
   =cut
   
 sub getSequenceDropDown {  sub getSequenceDropDown {
     my ($request,$symb)=@_;      my ($symb)=@_;
     my $result='<select name="selectpage">'."\n";      my $result='<select name="selectpage">'."\n";
     my ($titles,$symbx) = &getSymbMap($request);      my ($titles,$symbx) = &getSymbMap();
     my ($curpage)=&Apache::lonnet::decode_symb($symb);       my ($curpage)=&Apache::lonnet::decode_symb($symb); 
     my $ctr=0;      my $ctr=0;
     foreach (@$titles) {      foreach (@$titles) {
Line 4379  sub getSequenceDropDown { Line 4621  sub getSequenceDropDown {
     return $result;      return $result;
 }  }
   
   
   =pod 
   
   =item scantron_filenames
   
      Returns a list of the scantron files in the current course 
   
   =cut
   
 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'};
Line 4394  sub scantron_filenames { Line 4645  sub scantron_filenames {
     return @possiblenames;      return @possiblenames;
 }  }
   
   =pod 
   
   =item scantron_uploads
   
      Returns  html drop-down list of scantron files in current course.
   
    Arguments:
      $file2grade - filename to set as selected in the dropdown
   
   =cut
   
 sub scantron_uploads {  sub scantron_uploads {
     my ($file2grade) = @_;      my ($file2grade) = @_;
     my $result= '<select name="scantron_selectfile">';      my $result= '<select name="scantron_selectfile">';
Line 4405  sub scantron_uploads { Line 4667  sub scantron_uploads {
     return $result;      return $result;
 }  }
   
   =pod 
   
   =item scantron_scantab
   
     Returns html drop down of the scantron formats in the scantronformat.tab
     file.
   
   =cut
   
 sub scantron_scantab {  sub scantron_scantab {
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');      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";
Line 4419  sub scantron_scantab { Line 4690  sub scantron_scantab {
     return $result;      return $result;
 }  }
   
   =pod 
   
   =item scantron_CODElist
   
     Returns html drop down of the saved CODE lists from current course,
     generated from earlier printings.
   
   =cut
   
 sub scantron_CODElist {  sub scantron_CODElist {
     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};      my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
Line 4433  sub scantron_CODElist { Line 4713  sub scantron_CODElist {
     return $namechoice;      return $namechoice;
 }  }
   
   =pod 
   
   =item scantron_CODEunique
   
     Returns the html for "Each CODE to be used once" radio.
   
   =cut
   
 sub scantron_CODEunique {  sub scantron_CODEunique {
     my $result='<span style="white-space: nowrap;">      my $result='<span style="white-space: nowrap;">
                  <label><input type="radio" name="scantron_CODEunique"                   <label><input type="radio" name="scantron_CODEunique"
                         value="yes" checked="checked" /> Yes </label>                          value="yes" checked="checked" />'.&mt('Yes').' </label>
                 </span>                  </span>
                 <span style="white-space: nowrap;">                  <span style="white-space: nowrap;">
                  <label><input type="radio" name="scantron_CODEunique"                   <label><input type="radio" name="scantron_CODEunique"
                         value="no" /> No </label>                          value="no" />'.&mt('No').' </label>
                 </span>';                  </span>';
     return $result;      return $result;
 }  }
   
   =pod 
   
   =item scantron_selectphase
   
     Generates the initial screen to start the bubble sheet process.
     Allows for - starting a grading run.
                - downloading existing scan data (original, corrected
                                                   or skipped info)
   
                - uploading new scan data
   
    Arguments:
     $r          - The Apache request object
     $file2grade - name of the file that contain the scanned data to score
   
   =cut
   
 sub scantron_selectphase {  sub scantron_selectphase {
     my ($r,$file2grade) = @_;      my ($r,$file2grade) = @_;
     my ($symb)=&get_symb($r);      my ($symb)=&get_symb($r);
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $sequence_selector=&getSequenceDropDown($r,$symb);      my $sequence_selector=&getSequenceDropDown($symb);
     my $default_form_data=&defaultFormData($symb);      my $default_form_data=&defaultFormData($symb);
     my $grading_menu_button=&show_grading_menu_form($symb);      my $grading_menu_button=&show_grading_menu_form($symb);
     my $file_selector=&scantron_uploads($file2grade);      my $file_selector=&scantron_uploads($file2grade);
Line 4457  sub scantron_selectphase { Line 4762  sub scantron_selectphase {
     my $CODE_selector=&scantron_CODElist();      my $CODE_selector=&scantron_CODElist();
     my $CODE_unique=&scantron_CODEunique();      my $CODE_unique=&scantron_CODEunique();
     my $result;      my $result;
     #FIXME allow instructor to be able to download the scantron file  
     # and to upload it,      # Chunk of form to prompt for a file to grade and how:
   
     $result.= <<SCANTRONFORM;      $result.= <<SCANTRONFORM;
     <table width="100%" border="0">      <table width="100%" border="0">
     <tr>      <tr>
Line 4491  sub scantron_selectphase { Line 4797  sub scantron_selectphase {
     <td> Options: </td>      <td> Options: </td>
             <td>              <td>
        <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records</label> <br />         <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records</label> <br />
                <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all exisiting corrections</label> <br />                 <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all existing corrections</label> <br />
                <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label>                 <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label>
     </td>      </td>
           </tr>            </tr>
Line 4511  SCANTRONFORM Line 4817  SCANTRONFORM
     if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||      if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
         &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {          &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
   
    # Chunk of form to prompt for a scantron file upload.
   
         $r->print(<<SCANTRONFORM);          $r->print(<<SCANTRONFORM);
     <tr>      <tr>
       <td bgcolor="#777777">        <td bgcolor="#777777">
Line 4556  UPLOAD Line 4864  UPLOAD
     </tr>      </tr>
 SCANTRONFORM  SCANTRONFORM
     }      }
   
       # Chunk of the form that prompts to view a scoring office file,
       # corrected file, skipped records in a file.
   
     $r->print(<<SCANTRONFORM);      $r->print(<<SCANTRONFORM);
     <tr>      <tr>
       <form action='/adm/grades' name='scantron_download'>        <form action='/adm/grades' name='scantron_download'>
Line 4582  SCANTRONFORM Line 4894  SCANTRONFORM
     </tr>      </tr>
 SCANTRONFORM  SCANTRONFORM
   
     $r->print(<<SCANTRONFORM);      $r->print('<tr><td bgcolor="#777777">');
   </table>      &Apache::lonpickcode::code_list($r,2);
 $grading_menu_button      $r->print('</td></tr></table>');
 SCANTRONFORM      $r->print($grading_menu_button);
   
     return      return
 }  }
   
   =pod
   
   =item get_scantron_config
   
      Parse and return the scantron configuration line selected as a
      hash of configuration file fields.
   
    Arguments:
       which - the name of the configuration to parse from the file.
   
   
    Returns:
               If the named configuration is not in the file, an empty
               hash is returned.
       a hash with the fields
         name         - internal name for the this configuration setup
         description  - text to display to operator that describes this config
         CODElocation - if 0 or the string 'none'
                             - no CODE exists for this config
                        if -1 || the string 'letter'
                             - a CODE exists for this config and is
                               a string of letters
                        Unsupported value (but planned for future support)
                             if a positive integer
                                  - The CODE exists as the first n items from
                                    the question section of the form
                             if the string 'number'
                                  - The CODE exists for this config and is
                                    a string of numbers
         CODEstart   - (only matter if a CODE exists) column in the line where
                        the CODE starts
         CODElength  - length of the CODE
         IDstart     - column where the student ID number starts
         IDlength    - length of the student ID info
         Qstart      - column where the information from the bubbled
                       'questions' start
         Qlength     - number of columns comprising a single bubble line from
                       the sheet. (usually either 1 or 10)
         Qon         - either a single character representing the character used
                       to signal a bubble was chosen in the positional setup, or
                       the string 'letter' if the letter of the chosen bubble is
                       in the final, or 'number' if a number representing the
                       chosen bubble is in the file (1->A 0->J)
         Qoff        - the character used to represent that a bubble was
                       left blank
         PaperID     - if the scanning process generates a unique number for each
                       sheet scanned the column that this ID number starts in
         PaperIDlength - number of columns that comprise the unique ID number
                         for the sheet of paper
         FirstName   - column that the first name starts in
         FirstNameLength - number of columns that the first name spans
    
         LastName    - column that the last name starts in
         LastNameLength - number of columns that the last name spans
   
   =cut
   
 sub get_scantron_config {  sub get_scantron_config {
     my ($which) = @_;      my ($which) = @_;
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');      my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
Line 4622  sub get_scantron_config { Line 4990  sub get_scantron_config {
     return %config;      return %config;
 }  }
   
   =pod 
   
   =item username_to_idmap
   
       creates a hash keyed by student id with values of the corresponding
       student username:domain.
   
     Arguments:
   
       $classlist - reference to the class list hash. This is a hash
                    keyed by student name:domain  whose elements are references
                    to arrays containing various chunks of information
                    about the student. (See loncoursedata for more info).
   
     Returns
       %idmap - the constructed hash
   
   =cut
   
 sub username_to_idmap {  sub username_to_idmap {
     my ($classlist)= @_;      my ($classlist)= @_;
     my %idmap;      my %idmap;
Line 4632  sub username_to_idmap { Line 5019  sub username_to_idmap {
     return %idmap;      return %idmap;
 }  }
   
   =pod
   
   =item scantron_fixup_scanline
   
      Process a requested correction to a scanline.
   
     Arguments:
       $scantron_config   - hash from &get_scantron_config()
       $scan_data         - hash of correction information 
                             (see &scantron_getfile())
       $line              - existing scanline
       $whichline         - line number of the passed in scanline
       $field             - type of change to process 
                            (either 
                             'ID'     -> correct the student ID number
                             'CODE'   -> correct the CODE
                             'answer' -> fixup the submitted answers)
       
      $args               - hash of additional info,
                             - 'ID' 
                                  'newid' -> studentID to use in replacement
                                             of existing one
                             - 'CODE' 
                                  'CODE_ignore_dup' - set to true if duplicates
                                                      should be ignored.
                          'CODE' - is new code or 'use_unfound'
                                           if the existing unfound code should
                                           be used as is
                             - 'answer'
                                  'response' - new answer or 'none' if blank
                                  'question' - the bubble line to change
   
     Returns:
       $line - the modified scanline
   
     Side effects: 
       $scan_data - may be updated
   
   =cut
   
   
 sub scantron_fixup_scanline {  sub scantron_fixup_scanline {
     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;      my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
   
     if ($field eq 'ID') {      if ($field eq 'ID') {
  if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {   if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
     return ($line,1,'New value too large');      return ($line,1,'New value too large');
Line 4690  sub scantron_fixup_scanline { Line 5119  sub scantron_fixup_scanline {
     return $line;      return $line;
 }  }
   
   =pod
   
   =item scan_data
   
       Edit or look up  an item in the scan_data hash.
   
     Arguments:
       $scan_data  - The hash (see scantron_getfile)
       $key        - shorthand of the key to edit (actual key is
                     scantronfilename_key).
       $data        - New value of the hash entry.
       $delete      - If true, the entry is removed from the hash.
   
     Returns:
       The new value of the hash table field (undefined if deleted).
   
   =cut
   
   
 sub scan_data {  sub scan_data {
     my ($scan_data,$key,$value,$delete)=@_;      my ($scan_data,$key,$value,$delete)=@_;
     my $filename=$env{'form.scantron_selectfile'};      my $filename=$env{'form.scantron_selectfile'};
Line 4700  sub scan_data { Line 5148  sub scan_data {
     return $scan_data->{$filename.'_'.$key};      return $scan_data->{$filename.'_'.$key};
 }  }
   
   =pod 
   
   =item scantron_parse_scanline
   
     Decodes a scanline from the selected scantron file
   
    Arguments:
       line             - The text of the scantron file line to process
       whichline        - Line number
       scantron_config  - Hash describing the format of the scantron lines.
       scan_data        - Hash of extra information about the scanline
                          (see scantron_getfile for more information)
       just_header      - True if should not process question answers but only
                          the stuff to the left of the answers.
    Returns:
      Hash containing the result of parsing the scanline
   
      Keys are all proceeded by the string 'scantron.'
   
          CODE    - the CODE in use for this scanline
          useCODE - 1 if the CODE is invalid but it usage has been forced
                    by the operator
          CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
                               CODEs were selected, but the usage has been
                               forced by the operator
          ID  - student ID
          PaperID - if used, the ID number printed on the sheet when the 
                    paper was scanned
          FirstName - first name from the sheet
          LastName  - last name from the sheet
   
        if just_header was not true these key may also exist
   
          missingerror - a list of bubble ranges that are considered to be answers
                         to a single question that don't have any bubbles filled in.
                         Of the form questionnumber:firstbubblenumber:count.
          doubleerror  - a list of bubble ranges that are considered to be answers
                         to a single question that have more than one bubble filled in.
                         Of the form questionnumber::firstbubblenumber:count
      
                   In the above, count is the number of bubble responses in the
                   input line needed to represent the possible answers to the question.
                   e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
                   per line would have count = 2.
   
          maxquest     - the number of the last bubble line that was parsed
   
          (<number> starts at 1)
          <number>.answer - zero or more letters representing the selected
                            letters from the scanline for the bubble line 
                            <number>.
                            if blank there was either no bubble or there where
                            multiple bubbles, (consult the keys missingerror and
                            doubleerror if this is an error condition)
   
   =cut
   
 sub scantron_parse_scanline {  sub scantron_parse_scanline {
     my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_;      my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
     my %record;      my %record;
     my $questions=substr($line,$$scantron_config{'Qstart'}-1);      my $questions=substr($line,$$scantron_config{'Qstart'}-1);  # Answers
     my $data=substr($line,0,$$scantron_config{'Qstart'}-1);      my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
     if (!($$scantron_config{'CODElocation'} eq 0 ||      if (!($$scantron_config{'CODElocation'} eq 0 ||
   $$scantron_config{'CODElocation'} eq 'none')) {    $$scantron_config{'CODElocation'} eq 'none')) {
  if ($$scantron_config{'CODElocation'} < 0 ||   if ($$scantron_config{'CODElocation'} < 0 ||
Line 4734  sub scantron_parse_scanline { Line 5239  sub scantron_parse_scanline {
     $record{'scantron.LastName'}=      $record{'scantron.LastName'}=
  substr($data,$$scantron_config{'LastName'}-1,   substr($data,$$scantron_config{'LastName'}-1,
        $$scantron_config{'LastNamelength'});         $$scantron_config{'LastNamelength'});
     if ($justHeader) { return \%record; }      if ($just_header) { return \%record; }
   
     my @alphabet=('A'..'Z');      my @alphabet=('A'..'Z');
     my $questnum=0;      my $questnum=0;
       my $ansnum  =1; # Multiple 'answer lines'/question.
   
     while ($questions) {      while ($questions) {
    my $answers_needed = $bubble_lines_per_response{$questnum};
    my $answer_length  = $$scantron_config{'Qlength'} * $answers_needed;
   
   
   
  $questnum++;   $questnum++;
  my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});   my $currentquest = substr($questions,0,$answer_length);
  substr($questions,0,$$scantron_config{'Qlength'})='';   $questions       = substr($questions,0,$answer_length)='';
  if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }   if (length($currentquest) < $answer_length) { next; }
   
    # Qon letter implies for each slot in currentquest we have:
    #    ? or * for doubles a letter in A-Z for a bubble and
           #    about anything else (esp. a value of Qoff for missing
    #    bubbles.
   
   
  if ($$scantron_config{'Qon'} eq 'letter') {   if ($$scantron_config{'Qon'} eq 'letter') {
     if ($currentquest eq '?'  
  || $currentquest eq '*') {      if ($currentquest =~ /\?/
    || $currentquest =~ /\*/
    || (&occurence_count($currentquest, "[A-Z]") > 1)) {
  push(@{$record{'scantron.doubleerror'}},$questnum);   push(@{$record{'scantron.doubleerror'}},$questnum);
  $record{"scantron.$questnum.answer"}='';   for (my $ans = 0; $ans < $answers_needed; $ans++) { 
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
    }
   
     } elsif (!defined($currentquest)      } elsif (!defined($currentquest)
      || $currentquest eq $$scantron_config{'Qoff'}       || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest))
      || $currentquest !~ /^[A-Z]$/) {       || (&occurence_count($currentquest, "[A-Z]") == 0)) {
  $record{"scantron.$questnum.answer"}='';   for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {   if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
     push(@{$record{"scantron.missingerror"}},$questnum);      push(@{$record{"scantron.missingerror"}},$questnum);
       $ansnum += $answers_needed;
  }   }
   
     } else {      } else {
  $record{"scantron.$questnum.answer"}=$currentquest;   for (my $ans = 0; $ans < $answers_needed; $ans++) {
       $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
       $ansnum++;
    }
     }      }
   
    # Qon 'number' implies each slot gives a digit that indexes the
    #    the bubbles filled or Qoff or a non number for unbubbled lines.
           #    and *? for double bubbles on a line.
    #    these answers are also stored as letters.
   
  } elsif ($$scantron_config{'Qon'} eq 'number') {   } elsif ($$scantron_config{'Qon'} eq 'number') {
     if ($currentquest eq '?'      if ($currentquest =~ /\?/
  || $currentquest eq '*') {   || $currentquest =~ /\*/
    || (&occurence_count($currentquest, '\d') > 1)) {
  push(@{$record{'scantron.doubleerror'}},$questnum);   push(@{$record{'scantron.doubleerror'}},$questnum);
  $record{"scantron.$questnum.answer"}='';   for (my $ans = 0; $ans < $answers_needed; $ans++) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
    }
   
     } elsif (!defined($currentquest)      } elsif (!defined($currentquest)
      || $currentquest eq $$scantron_config{'Qoff'}        || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest)) 
      || $currentquest !~ /^\d$/) {       || (&occurence_count($currentquest, '\d') == 0)) {
  $record{"scantron.$questnum.answer"}='';   for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {   if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
     push(@{$record{"scantron.missingerror"}},$questnum);      push(@{$record{"scantron.missingerror"}},$questnum);
       $ansnum += $answers_needed;
  }   }
   
     } else {      } else {
  # wrap zero back to J   $currentquest = &digits_to_letters($currentquest);
  if ($currentquest eq '0') {   for (my $ans =0; $ans < $answers_needed; $ans++) {
     $record{"scantron.$questnum.answer"}=      $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
  $alphabet[9];      $ansnum++;
  } else {  
     $record{"scantron.$questnum.answer"}=  
  $alphabet[$currentquest-1];  
  }   }
     }      }
  } else {   } else {
   
       # Otherwise there's a positional notation;
       # each bubble line requires Qlength items, and there are filled in
       # bubbles for each case where there 'Qon' characters.
       #
   
     my @array=split($$scantron_config{'Qon'},$currentquest,-1);      my @array=split($$scantron_config{'Qon'},$currentquest,-1);
     if (length($array[0]) eq $$scantron_config{'Qlength'}) {  
  $record{"scantron.$questnum.answer"}='';      # If the split only  giveas us one element.. the full length of the
       # answser string, no bubbles are filled in:
   
       if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
    for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {   if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
     push(@{$record{"scantron.missingerror"}},$questnum);      push(@{$record{"scantron.missingerror"}},$questnum);
  }   }
     } else {      } elsif (scalar(@array) lt 2) {
  $record{"scantron.$questnum.answer"}=  
     $alphabet[length($array[0])];   my $location      = length($array[0]);
    my $line_num      = $location / $$scantron_config{'Qlength'};
    my $bubble        = $alphabet[$location % $$scantron_config{'Qlength'}];
   
    for (my $ans = 0; $ans < $answers_needed; $ans++) {
       if ($ans eq $line_num) {
    $record{"scantron.$ansnum.answer"} = $bubble;
       } else {
    $record{"scantron.$ansnum.answer"} = ' ';
       }
       $ansnum++;
    }
     }      }
     if (scalar(@array) gt 2) {      #  If there's more than one instance of a bubble character
       #  That's a double bubble; with positional notation we can
       #  record all the bubbles filled in as well as the 
       #  fact this response consists of multiple bubbles.
       #
       else {
  push(@{$record{'scantron.doubleerror'}},$questnum);   push(@{$record{'scantron.doubleerror'}},$questnum);
   
    my $first_answer = $ansnum;
    for (my $ans =0; $ans < $answers_needed; $ans++) {
       $record{"scantron.$ansnum.answer"} = '';
       $ans++;
    }
   
  my @ans=@array;   my @ans=@array;
  my $i=length($ans[0]);shift(@ans);   my $i=length($ans[0]);shift(@ans);
  while ($#ans) {   while ($#ans) {
     $i+=length($ans[0])+1;      $i+=length($ans[0])+1;
     $record{"scantron.$questnum.answer"}.=$alphabet[$i];      my $line   = $i/$$scantron_config{'Qlength'} + $first_answer;
       my $bubble = $i%$$scantron_config{'Qlength'};
   
       $record{"scantron.$line.answer"}.=$alphabet[$bubble];
     shift(@ans);      shift(@ans);
  }   }
     }      }
Line 4807  sub scantron_parse_scanline { Line 5395  sub scantron_parse_scanline {
     return \%record;      return \%record;
 }  }
   
   =pod
   
   =item scantron_add_delay
   
      Adds an error message that occurred during the grading phase to a
      queue of messages to be shown after grading pass is complete
   
    Arguments:
      $delayqueue  - arrary ref of hash ref of error messages
      $scanline    - the scanline that caused the error
      $errormesage - the error message
      $errorcode   - a numeric code for the error
   
    Side Effects:
      updates the $delayqueue to have a new hash ref of the error
   
   =cut
   
 sub scantron_add_delay {  sub scantron_add_delay {
     my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;      my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
     push(@$delayqueue,      push(@$delayqueue,
Line 4815  sub scantron_add_delay { Line 5421  sub scantron_add_delay {
  );   );
 }  }
   
   =pod
   
   =item scantron_find_student
   
      Finds the username for the current scanline
   
     Arguments:
      $scantron_record - hash result from scantron_parse_scanline
      $scan_data       - hash of correction information 
                         (see &scantron_getfile() form more information)
      $idmap           - hash from &username_to_idmap()
      $line            - number of current scanline
    
     Returns:
      Either 'username:domain' or undef if unknown
   
   =cut
   
 sub scantron_find_student {  sub scantron_find_student {
     my ($scantron_record,$scan_data,$idmap,$line)=@_;      my ($scantron_record,$scan_data,$idmap,$line)=@_;
     my $scanID=$$scantron_record{'scantron.ID'};      my $scanID=$$scantron_record{'scantron.ID'};
Line 4829  sub scantron_find_student { Line 5453  sub scantron_find_student {
     return undef;      return undef;
 }  }
   
   =pod
   
   =item scantron_filter
   
      Filter sub for lonnavmaps, filters out hidden resources if ignore
      hidden resources was selected
   
   =cut
   
 sub scantron_filter {  sub scantron_filter {
     my ($curres)=@_;      my ($curres)=@_;
   
Line 4845  sub scantron_filter { Line 5478  sub scantron_filter {
     return 0;      return 0;
 }  }
   
   =pod
   
   =item scantron_process_corrections
   
      Gets correction information out of submitted form data and corrects
      the scanline
   
   =cut
   
 sub scantron_process_corrections {  sub scantron_process_corrections {
     my ($r) = @_;      my ($r) = @_;
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
Line 4902  sub scantron_process_corrections { Line 5544  sub scantron_process_corrections {
     }      }
 }  }
   
   =pod
   
   =item reset_skipping_status
   
      Forgets the current set of remember skipped scanlines (and thus
      reverts back to considering all lines in the
      scantron_skipped_<filename> file)
   
   =cut
   
 sub reset_skipping_status {  sub reset_skipping_status {
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
     &scan_data($scan_data,'remember_skipping',undef,1);      &scan_data($scan_data,'remember_skipping',undef,1);
     &scantron_putfile(undef,$scan_data);      &scantron_putfile(undef,$scan_data);
 }  }
   
   =pod
   
   =item start_skipping
   
      Marks a scanline to be skipped. 
   
   =cut
   
 sub start_skipping {  sub start_skipping {
     my ($scan_data,$i)=@_;      my ($scan_data,$i)=@_;
     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));      my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
Line 4919  sub start_skipping { Line 5579  sub start_skipping {
     &scan_data($scan_data,'remember_skipping',join(':',%remembered));      &scan_data($scan_data,'remember_skipping',join(':',%remembered));
 }  }
   
   =pod
   
   =item should_be_skipped
   
      Checks whether a scanline should be skipped.
   
   =cut
   
 sub should_be_skipped {  sub should_be_skipped {
     my ($scanlines,$scan_data,$i)=@_;      my ($scanlines,$scan_data,$i)=@_;
     if ($env{'form.scantron_options_redo'} !~ /^redo_/) {      if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
Line 4934  sub should_be_skipped { Line 5602  sub should_be_skipped {
     return 1;      return 1;
 }  }
   
   =pod
   
   =item remember_current_skipped
   
      Discovers what scanlines are in the scantron_skipped_<filename>
      file and remembers them into scan_data for later use.
   
   =cut
   
 sub remember_current_skipped {  sub remember_current_skipped {
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
     my %to_remember;      my %to_remember;
Line 4947  sub remember_current_skipped { Line 5624  sub remember_current_skipped {
     &scantron_putfile(undef,$scan_data);      &scantron_putfile(undef,$scan_data);
 }  }
   
   =pod
   
   =item check_for_error
   
       Checks if there was an error when attempting to remove a specific
       scantron_.. bubble sheet data file. Prints out an error if
       something went wrong.
   
   =cut
   
 sub check_for_error {  sub check_for_error {
     my ($r,$result)=@_;      my ($r,$result)=@_;
     if ($result ne 'ok' && $result ne 'not_found' ) {      if ($result ne 'ok' && $result ne 'not_found' ) {
Line 4954  sub check_for_error { Line 5641  sub check_for_error {
     }      }
 }  }
   
   =pod
   
   =item scantron_warning_screen
   
      Interstitial screen to make sure the operator has selected the
      correct options before we start the validation phase.
   
   =cut
   
 sub scantron_warning_screen {  sub scantron_warning_screen {
     my ($button_text)=@_;      my ($button_text)=@_;
     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});      my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
Line 4986  $CODElist Line 5682  $CODElist
 STUFF  STUFF
 }  }
   
   =pod
   
   =item scantron_do_warning
   
      Check if the operator has picked something for all required
      fields. Error out if something is missing.
   
   =cut
   
 sub scantron_do_warning {  sub scantron_do_warning {
     my ($r)=@_;      my ($r)=@_;
     my ($symb)=&get_symb($r);      my ($symb)=&get_symb($r);
Line 5017  STUFF Line 5722  STUFF
     return '';      return '';
 }  }
   
   =pod
   
   =item scantron_form_start
   
       html hidden input for remembering all selected grading options
   
   =cut
   
 sub scantron_form_start {  sub scantron_form_start {
     my ($max_bubble)=@_;      my ($max_bubble)=@_;
     my $result= <<SCANTRONFORM;      my $result= <<SCANTRONFORM;
Line 5031  sub scantron_form_start { Line 5744  sub scantron_form_start {
   <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />    <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
   <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />    <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
 SCANTRONFORM  SCANTRONFORM
   
     my $line = 0;
       while (defined($env{"form.scantron.bubblelines.$line"})) {
    &Apache::lonnet::logthis("Saving chunk for $line");
          my $chunk =
      '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
          $chunk .=
      '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
          $result .= $chunk;
          $line++;
      }
     return $result;      return $result;
 }  }
   
   =pod
   
   =item scantron_validate_file
   
       Dispatch routine for doing validation of a bubble sheet data file.
   
       Also processes any necessary information resets that need to
       occur before validation begins (ignore previous corrections,
       restarting the skipped records processing)
   
   =cut
   
 sub scantron_validate_file {  sub scantron_validate_file {
     my ($r) = @_;      my ($r) = @_;
     my ($symb)=&get_symb($r);      my ($symb)=&get_symb($r);
Line 5041  sub scantron_validate_file { Line 5777  sub scantron_validate_file {
     my $default_form_data=&defaultFormData($symb);      my $default_form_data=&defaultFormData($symb);
           
     # do the detection of only doing skipped records first befroe we delete      # do the detection of only doing skipped records first befroe we delete
     # them  when doing the corrections reset      # them when doing the corrections reset
     if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {      if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
  &reset_skipping_status();   &reset_skipping_status();
     }      }
Line 5060  sub scantron_validate_file { Line 5796  sub scantron_validate_file {
     if ($env{'form.scantron_corrections'}) {      if ($env{'form.scantron_corrections'}) {
  &scantron_process_corrections($r);   &scantron_process_corrections($r);
     }      }
     $r->print("<p>Gathering neccessary info.</p>");$r->rflush();      $r->print("<p>Gathering necessary info.</p>");$r->rflush();
     #get the student pick code ready      #get the student pick code ready
     $r->print(&Apache::loncommon::studentbrowser_javascript());      $r->print(&Apache::loncommon::studentbrowser_javascript());
     my $max_bubble=&scantron_get_maxbubble();      my $max_bubble=&scantron_get_maxbubble();
Line 5077  sub scantron_validate_file { Line 5813  sub scantron_validate_file {
     }      }
     my $currentphase=$env{'form.validatepass'};      my $currentphase=$env{'form.validatepass'};
   
       &Apache::lonnet::logthis("Phase: $currentphase");
   
     my $stop=0;      my $stop=0;
     while (!$stop && $currentphase < scalar(@validate_phases)) {      while (!$stop && $currentphase < scalar(@validate_phases)) {
  $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");   $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");
Line 5117  STUFF Line 5855  STUFF
     return '';      return '';
 }  }
   
   
   =pod
   
   =item scantron_remove_file
   
      Removes the requested bubble sheet data file, makes sure that
      scantron_original_<filename> is never removed
   
   
   =cut
   
 sub scantron_remove_file {  sub scantron_remove_file {
     my ($which)=@_;      my ($which)=@_;
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
Line 5131  sub scantron_remove_file { Line 5880  sub scantron_remove_file {
     return &Apache::lonnet::removeuserfile($cname,$cdom,$file);      return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
 }  }
   
   
   =pod
   
   =item scantron_remove_scan_data
   
      Removes all scan_data correction for the requested bubble sheet
      data file.  (In the case that both the are doing skipped records we need
      to remember the old skipped lines for the time being so that element
      persists for a while.)
   
   =cut
   
 sub scantron_remove_scan_data {  sub scantron_remove_scan_data {
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
Line 5153  sub scantron_remove_scan_data { Line 5914  sub scantron_remove_scan_data {
     return $result;      return $result;
 }  }
   
   
   =pod
   
   =item scantron_getfile
   
       Fetches the requested bubble sheet data file (all 3 versions), and
       the scan_data hash
     
     Arguments:
       None
   
     Returns:
       2 hash references
   
        - first one has 
            orig      -
            corrected -
            skipped   -  each of which points to an array ref of the specified
                         file broken up into individual lines
            count     - number of scanlines
    
        - second is the scan_data hash possible keys are
          ($number refers to scanline numbered $number and thus the key affects
           only that scanline
           $bubline refers to the specific bubble line element and the aspects
           refers to that specific bubble line element)
   
          $number.user - username:domain to use
          $number.CODE_ignore_dup 
                       - ignore the duplicate CODE error 
          $number.useCODE
                       - use the CODE in the scanline as is
          $number.no_bubble.$bubline
                       - it is valid that there is no bubbled in bubble
                         at $number $bubline
          remember_skipping
                       - a frozen hash containing keys of $number and values
                         of either 
                           1 - we are on a 'do skipped records pass' and plan
                               on processing this line
                           2 - we are on a 'do skipped records pass' and this
                               scanline has been marked to skip yet again
   
   =cut
   
 sub scantron_getfile {  sub scantron_getfile {
     #FIXME really would prefer a scantron directory      #FIXME really would prefer a scantron directory
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
Line 5185  sub scantron_getfile { Line 5991  sub scantron_getfile {
     return (\%scanlines,\%scan_data);      return (\%scanlines,\%scan_data);
 }  }
   
   =pod
   
   =item lonnet_putfile
   
      Wrapper routine to call &Apache::lonnet::finishuserfileupload
   
    Arguments:
      $contents - data to store
      $filename - filename to store $contents into
   
    Returns:
      result value from &Apache::lonnet::finishuserfileupload
   
   =cut
   
 sub lonnet_putfile {  sub lonnet_putfile {
     my ($contents,$filename)=@_;      my ($contents,$filename)=@_;
     my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};      my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
Line 5194  sub lonnet_putfile { Line 6015  sub lonnet_putfile {
   
 }  }
   
   =pod
   
   =item scantron_putfile
   
       Stores the current version of the bubble sheet data files, and the
       scan_data hash. (Does not modify the original version only the
       corrected and skipped versions.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
   
   =cut
   
 sub scantron_putfile {  sub scantron_putfile {
     my ($scanlines,$scan_data) = @_;      my ($scanlines,$scan_data) = @_;
     #FIXME really would prefer a scantron directory      #FIXME really would prefer a scantron directory
Line 5214  sub scantron_putfile { Line 6051  sub scantron_putfile {
     &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);      &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
 }  }
   
   =pod
   
   =item scantron_get_line
   
      Returns the correct version of the scanline
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - number of the requested line (starts at 0)
   
    Returns:
      A scanline, (either the original or the corrected one if it
      exists), or undef if the requested scanline should be
      skipped. (Either because it's an skipped scanline, or it's an
      unskipped scanline and we are not doing a 'do skipped scanlines'
      pass.
   
   =cut
   
 sub scantron_get_line {  sub scantron_get_line {
     my ($scanlines,$scan_data,$i)=@_;      my ($scanlines,$scan_data,$i)=@_;
     if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }      if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
Line 5222  sub scantron_get_line { Line 6081  sub scantron_get_line {
     return $scanlines->{'orig'}[$i];       return $scanlines->{'orig'}[$i]; 
 }  }
   
   =pod
   
   =item scantron_todo_count
   
       Counts the number of scanlines that need processing.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
   
    Returns:
       $count - number of scanlines to process
   
   =cut
   
 sub get_todo_count {  sub get_todo_count {
     my ($scanlines,$scan_data)=@_;      my ($scanlines,$scan_data)=@_;
     my $count=0;      my $count=0;
Line 5233  sub get_todo_count { Line 6109  sub get_todo_count {
     return $count;      return $count;
 }  }
   
   =pod
   
   =item scantron_put_line
   
       Updates the 'corrected' or 'skipped' versions of the bubble sheet
       data file.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - line number to update
       $newline   - contents of the updated scanline
       $skip      - if true make the line for skipping and update the
                    'skipped' file
   
   =cut
   
 sub scantron_put_line {  sub scantron_put_line {
     my ($scanlines,$scan_data,$i,$newline,$skip)=@_;      my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
     if ($skip) {      if ($skip) {
Line 5243  sub scantron_put_line { Line 6138  sub scantron_put_line {
     $scanlines->{'corrected'}[$i]=$newline;      $scanlines->{'corrected'}[$i]=$newline;
 }  }
   
   =pod
   
   =item scantron_clear_skip
   
      Remove a line from the 'skipped' file
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - line number to update
   
   =cut
   
 sub scantron_clear_skip {  sub scantron_clear_skip {
     my ($scanlines,$scan_data,$i)=@_;      my ($scanlines,$scan_data,$i)=@_;
     if (exists($scanlines->{'skipped'}[$i])) {      if (exists($scanlines->{'skipped'}[$i])) {
Line 5252  sub scantron_clear_skip { Line 6162  sub scantron_clear_skip {
     return 0;      return 0;
 }  }
   
   =pod
   
   =item scantron_filter_not_exam
   
      Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
      filter out resources that are not marked as 'exam' mode
   
   =cut
   
 sub scantron_filter_not_exam {  sub scantron_filter_not_exam {
     my ($curres)=@_;      my ($curres)=@_;
           
Line 5268  sub scantron_filter_not_exam { Line 6187  sub scantron_filter_not_exam {
     return 0;      return 0;
 }  }
   
   =pod
   
   =item scantron_validate_sequence
   
       Validates the selected sequence, checking for resource that are
       not set to exam mode.
   
   =cut
   
 sub scantron_validate_sequence {  sub scantron_validate_sequence {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
   
Line 5291  sub scantron_validate_sequence { Line 6219  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 5301  sub scantron_validate_ID { Line 6238  sub scantron_validate_ID {
     #get scantron line setup      #get scantron line setup
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
       
       &scantron_get_maxbubble(); # parse needs the bubble_lines.. array.
   
     my %found=('ids'=>{},'usernames'=>{});      my %found=('ids'=>{},'usernames'=>{});
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {      for (my $i=0;$i<=$scanlines->{'count'};$i++) {
Line 5353  sub scantron_validate_ID { Line 6292  sub scantron_validate_ID {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
   =pod
   
   =item scantron_get_correction
   
      Builds the interface screen to interact with the operator to fix a
      specific error condition in a specific scanline
   
    Arguments:
       $r           - Apache request object
       $i           - number of the current scanline
       $scan_record - hash ref as returned from &scantron_parse_scanline()
       $scan_config - hash ref as returned from &get_scantron_config()
       $line        - full contents of the current scanline
       $error       - error condition, valid values are
                      'incorrectCODE', 'duplicateCODE',
                      'doublebubble', 'missingbubble',
                      'duplicateID', 'incorrectID'
       $arg         - extra information needed
          For errors:
            - duplicateID   - paper number that this studentID was seen before on
            - duplicateCODE - array ref of the paper numbers this CODE was
                              seen on before
            - incorrectCODE - current incorrect CODE 
            - doublebubble  - array ref of the bubble lines that have double
                              bubble errors
            - missingbubble - array ref of the bubble lines that have missing
                              bubble errors
   
   =cut
   
 sub scantron_get_correction {  sub scantron_get_correction {
     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;      my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
   
 #FIXME in the case of a duplicated ID the previous line, probaly need  #FIXME in the case of a duplicated ID the previous line, probably need
 #to show both the current line and the previous one and allow skipping  #to show both the current line and the previous one and allow skipping
 #the previous one or the current one  #the previous one or the current one
   
Line 5455  ENDSCRIPT Line 6424  ENDSCRIPT
  $r->print($message);   $r->print($message);
  $r->print("<p>Please indicate which bubble should be used for grading</p>");   $r->print("<p>Please indicate which bubble should be used for grading</p>");
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my $selected=$$scan_record{"scantron.$question.answer"};  
     &scantron_bubble_selector($r,$scan_config,$question,split('',$selected));      my $selected  = &get_response_bubbles($scan_record, $question);
       &scantron_bubble_selector($r,$scan_config,$question,
         split('',$selected));
  }   }
     } elsif ($error eq 'missingbubble') {      } elsif ($error eq 'missingbubble') {
  $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");   $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");
Line 5466  ENDSCRIPT Line 6437  ENDSCRIPT
  $r->print('<input type="hidden" name="scantron_questions" value="'.   $r->print('<input type="hidden" name="scantron_questions" value="'.
   join(',',@{$arg}).'" />');    join(',',@{$arg}).'" />');
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my $selected=$$scan_record{"scantron.$question.answer"};      my $selected = &get_response_bubbles($scan_record, $question);
     &scantron_bubble_selector($r,$scan_config,$question);      &scantron_bubble_selector($r,$scan_config,$question);
  }   }
     } else {      } else {
Line 5476  ENDSCRIPT Line 6447  ENDSCRIPT
   
 }  }
   
   =pod
   
   =item scantron_bubble_selector
     
      Generates the html radiobuttons to correct a single bubble line
      possibly showing the existing the selected bubbles if known
   
    Arguments:
       $r           - Apache request object
       $scan_config - hash from &get_scantron_config()
       $quest       - number of the bubble line to make a corrector for
       $selected    - array of letters of previously selected bubbles
   
   =cut
   
 sub scantron_bubble_selector {  sub scantron_bubble_selector {
     my ($r,$scan_config,$quest,@selected)=@_;      my ($r,$scan_config,$quest,@selected)=@_;
     my $max=$$scan_config{'Qlength'};      my $max=$$scan_config{'Qlength'};
   
     my $scmode=$$scan_config{'Qon'};      my $scmode=$$scan_config{'Qon'};
   
   
     if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }           if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }     
   
       my $response = $quest-1;
       my $lines = $bubble_lines_per_response{$response};
       &Apache::lonnet::logthis("Question $quest, lines: $lines");
   
       my $total_lines = $lines*2;
     my @alphabet=('A'..'Z');      my @alphabet=('A'..'Z');
     $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>");      $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");
     for (my $i=0;$i<$max+1;$i++) {  
  $r->print("\n".'<td align="center">');      for (my $l = 0; $l < $lines; $l++) {
  if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }   if ($l != 0) {
  else { $r->print('&nbsp;'); }      $r->print('<tr>');
  $r->print('</td>');   }
     }  
     $r->print('</tr><tr>');   # FIXME:  This loop probably has to be considerably more clever for
     for (my $i=0;$i<$max;$i++) {   #  multiline bubbles: User can multibubble by having bubbles in
  $r->print("\n".   #  several lines.  User can skip lines legitimately etc. etc.
   '<td><label><input type="radio" name="scantron_correct_Q_'.  
   $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");   for (my $i=0;$i<$max;$i++) {
     }      $r->print("\n".'<td align="center">');
     $r->print('<td><label><input type="radio" name="scantron_correct_Q_'.      if ($selected[0] eq $alphabet[$i]) { 
    $r->print('X'); 
    shift(@selected) ;
       } else { 
    $r->print('&nbsp;'); 
       }
       $r->print('</td>');
       
    }
   
    if ($l == 0) {
       my $lspan = $total_lines * 2;   #  2 table rows per bubble line.
   
       $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'.
       $quest.'" value="none" /> No bubble </label></td>');        $quest.'" value="none" /> No bubble </label></td>');
     $r->print('</tr></table>');  
    }
   
    $r->print('</tr><tr>');
   
    # FIXME: This may have to be a bit more clever for
    #        multiline questions (different values e.g..).
   
    for (my $i=0;$i<$max;$i++) {
       $r->print("\n".
         '<td><label><input type="radio" name="scantron_correct_Q_'.
         $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
    }
    $r->print('</tr>');
   
       
       }
       $r->print('</table>');
 }  }
   
   =pod
   
   =item num_matches
   
      Counts the number of characters that are the same between the two arguments.
   
    Arguments:
      $orig - CODE from the scanline
      $code - CODE to match against
   
    Returns:
      $count - integer count of the number of same characters between the
               two arguments
   
   =cut
   
 sub num_matches {  sub num_matches {
     my ($orig,$code) = @_;      my ($orig,$code) = @_;
     my @code=split(//,$code);      my @code=split(//,$code);
Line 5513  sub num_matches { Line 6552  sub num_matches {
     return $same;      return $same;
 }  }
   
   =pod
   
   =item scantron_get_closely_matching_CODEs
   
      Cycles through all CODEs and finds the set that has the greatest
      number of same characters as the provided CODE
   
    Arguments:
      $allcodes - hash ref returned by &get_codes()
      $CODE     - CODE from the current scanline
   
    Returns:
      2 element list
       - first elements is number of how closely matching the best fit is 
         (5 means best set has 5 matching characters)
       - second element is an arrary ref containing the set of valid CODEs
         that best fit the passed in CODE
   
   =cut
   
 sub scantron_get_closely_matching_CODEs {  sub scantron_get_closely_matching_CODEs {
     my ($allcodes,$CODE)=@_;      my ($allcodes,$CODE)=@_;
     my @CODEs;      my @CODEs;
Line 5523  sub scantron_get_closely_matching_CODEs Line 6582  sub scantron_get_closely_matching_CODEs
     return ($#CODEs,$CODEs[-1]);      return ($#CODEs,$CODEs[-1]);
 }  }
   
   =pod
   
   =item get_codes
   
      Builds a hash which has keys of all of the valid CODEs from the selected
      set of remembered CODEs.
   
    Arguments:
     $old_name - name of the set of remembered CODEs
     $cdom     - domain of the course
     $cnum     - internal course name
   
    Returns:
     %allcodes - keys are the valid CODEs, values are all 1
   
   =cut
   
 sub get_codes {  sub get_codes {
     my ($old_name, $cdom, $cnum) = @_;      my ($old_name, $cdom, $cnum) = @_;
     if (!$old_name) {      if (!$old_name) {
Line 5545  sub get_codes { Line 6621  sub get_codes {
     return %allcodes;      return %allcodes;
 }  }
   
   =pod
   
   =item scantron_validate_CODE
   
      Validates all scanlines in the selected file to not have any
      invalid or underspecified CODEs and that none of the codes are
      duplicated if this was requested.
   
   =cut
   
 sub scantron_validate_CODE {  sub scantron_validate_CODE {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
Line 5562  sub scantron_validate_CODE { Line 6648  sub scantron_validate_CODE {
   
     my %allcodes=&get_codes();      my %allcodes=&get_codes();
   
       &scantron_get_maxbubble(); # parse needs the lines per response array.
   
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {      for (my $i=0;$i<=$scanlines->{'count'};$i++) {
  my $line=&scantron_get_line($scanlines,$scan_data,$i);   my $line=&scantron_get_line($scanlines,$scan_data,$i);
Line 5596  sub scantron_validate_CODE { Line 6684  sub scantron_validate_CODE {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
   =pod
   
   =item scantron_validate_doublebubble
   
      Validates all scanlines in the selected file to not have any
      bubble lines with multiple bubbles marked.
   
   =cut
   
 sub scantron_validate_doublebubble {  sub scantron_validate_doublebubble {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
     #get student info      #get student info
Line 5605  sub scantron_validate_doublebubble { Line 6702  sub scantron_validate_doublebubble {
     #get scantron line setup      #get scantron line setup
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
   
       &scantron_get_maxbubble(); # parse needs the bubble line array.
   
     for (my $i=0;$i<=$scanlines->{'count'};$i++) {      for (my $i=0;$i<=$scanlines->{'count'};$i++) {
  my $line=&scantron_get_line($scanlines,$scan_data,$i);   my $line=&scantron_get_line($scanlines,$scan_data,$i);
  if ($line=~/^[\s\cz]*$/) { next; }   if ($line=~/^[\s\cz]*$/) { next; }
Line 5619  sub scantron_validate_doublebubble { Line 6719  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'} and 
      $env{'form.scantron.first_bubble_line.n'}
      which are the total number of bubble, lines, the number of bubble
      lines for reponse n and number of the first bubble line for response n.
   
   =cut
   
 sub scantron_get_maxbubble {      sub scantron_get_maxbubble {    
       &Apache::lonnet::logthis("get_max_bubble");
     if (defined($env{'form.scantron_maxbubble'}) &&      if (defined($env{'form.scantron_maxbubble'}) &&
  $env{'form.scantron_maxbubble'}) {   $env{'form.scantron_maxbubble'}) {
    &Apache::lonnet::logthis("cached");
    &restore_bubble_lines();
  return $env{'form.scantron_maxbubble'};   return $env{'form.scantron_maxbubble'};
     }      }
       &Apache::lonnet::logthis("computing");
   
     my $navmap=Apache::lonnavmaps::navmap->new();      my (undef, undef, $sequence) =
     my (undef,undef,$sequence)=  
  &Apache::lonnet::decode_symb($env{'form.selectpage'});   &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
       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);
   
     &Apache::lonxml::clear_problem_counter();      &Apache::lonxml::clear_problem_counter();
   
       my $uname       = $env{'form.student'};
       my $udom        = $env{'form.userdom'};
       my $cid         = $env{'request.course.id'};
       my $total_lines = 0;
       %bubble_lines_per_response = ();
       %first_bubble_line         = ();
   
     
       my $response_number = 0;
       my $bubble_line     = 0;
     foreach my $resource (@resources) {      foreach my $resource (@resources) {
    my $symb = $resource->symb();
    &Apache::lonxml::clear_bubble_lines_for_part();
  my $result=&Apache::lonnet::ssi($resource->src(),   my $result=&Apache::lonnet::ssi($resource->src(),
  ('symb' => $resource->symb()));   ('symb' => $resource->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);
   
   
   
    foreach my $part_id (@{$analysis{'parts'}}) {
       my ($trash, $part) = split(/\./, $part_id);
   
       my $lines = $analysis{"$part_id.bubble_lines"}[0];
   
       # TODO - make this a persistent hash not an array.
   
   
       $first_bubble_line{$response_number}           = $bubble_line;
       $bubble_lines_per_response{$response_number}   = $lines;
       $response_number++;
   
       $bubble_line +=  $lines;
       $total_lines +=  $lines;
    }
   
     }      }
     &Apache::lonnet::delenv('scantron\.');      &Apache::lonnet::delenv('scantron\.');
     $env{'form.scantron_maxbubble'} =  
  &Apache::lonxml::get_problem_counter()-1;  
   
       &save_bubble_lines();
       $env{'form.scantron_maxbubble'} =
    $total_lines;
     return $env{'form.scantron_maxbubble'};      return $env{'form.scantron_maxbubble'};
 }  }
   
   =pod
   
   =item scantron_validate_missingbubbles
   
      Validates all scanlines in the selected file to not have any
       answers that don't have bubbles that have not been verified
       to be bubble free.
   
   =cut
   
 sub scantron_validate_missingbubbles {  sub scantron_validate_missingbubbles {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
     #get student info      #get student info
Line 5677  sub scantron_validate_missingbubbles { Line 6848  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'});
Line 5711  SCANTRONFORM Line 6906  SCANTRONFORM
     my $start=&Time::HiRes::time();      my $start=&Time::HiRes::time();
     my $i=-1;      my $i=-1;
     my ($uname,$udom,$started);      my ($uname,$udom,$started);
   
       &scantron_get_maxbubble(); # Need the bubble lines array to parse.
   
     while ($i<$scanlines->{'count'}) {      while ($i<$scanlines->{'count'}) {
   ($uname,$udom)=('','');    ($uname,$udom)=('','');
   $i++;    $i++;
Line 5761  SCANTRONFORM Line 6959  SCANTRONFORM
     }      }
     my $result=&Apache::lonnet::ssi($resource->src(),%form);      my $result=&Apache::lonnet::ssi($resource->src(),%form);
     if ($result ne '') {      if ($result ne '') {
  &Apache::lonnet::logthis("scantron grading error -> $result");  
  &Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $env{'request.course.id'} url ".$resource->src());  
     }      }
     if (&Apache::loncommon::connection_aborted($r)) { last; }      if (&Apache::loncommon::connection_aborted($r)) { last; }
  }   }
Line 5781  SCANTRONFORM Line 6977  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 5817  UPLOAD Line 7021  UPLOAD
     return '';      return '';
 }  }
   
   =pod
   
   =item scantron_upload_scantron_data_save
   
      Adds a provided bubble information data file to the course if user
      has the correct privileges to do so.  
   
   =cut
   
 sub scantron_upload_scantron_data_save {  sub scantron_upload_scantron_data_save {
     my($r)=@_;      my($r)=@_;
     my ($symb)=&get_symb($r,1);      my ($symb)=&get_symb($r,1);
Line 5872  sub scantron_upload_scantron_data_save { Line 7085  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 5880  sub valid_file { Line 7101  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 5916  DOWNLOAD Line 7147  DOWNLOAD
     return '';      return '';
 }  }
   
   =pod
   
   =back
   
   =cut
   
 #-------- end of section for handling grading scantron forms -------  #-------- end of section for handling grading scantron forms -------
 #  #
 #-------------------------------------------------------------------  #-------------------------------------------------------------------
Line 5946  sub savedState { Line 7183  sub savedState {
     return \%savedState;      return \%savedState;
 }  }
   
 #--- Displays the main menu page -------  sub grading_menu {
 sub gradingmenu {      my ($request) = @_;
       my ($symb)=&get_symb($request);
       if (!$symb) {return '';}
       my $probTitle = &Apache::lonnet::gettitle($symb);
       my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
   
       #
       # Define menu data
       $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
       my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
       $request->print($table);
       my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
                     'handgrade'=>$hdgrade,
                     'probTitle'=>$probTitle,
                     'command'=>'submit_options',
                     'saveState'=>"",
                     'gradingMenu'=>1,
                     'showgrading'=>"yes");
       my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       my @menu = ({ url => $url,
                        name => &mt('Manual Grading/View Submissions'),
                        short_description => 
       &mt('Start the process of hand grading submissions.'),
                    });
       $fields{'command'} = 'csvform';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Upload Scores'),
                      short_description => 
               &mt('Specify a file containing the class scores for current resource.')});
       $fields{'command'} = 'processclicker';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Process Clicker'),
                      short_description => 
               &mt('Specify a file containing the clicker information for this resource.')});
       $fields{'command'} = 'scantron_selectphase';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Grade/Manage Scantron Forms'),
                      short_description => 
               &mt('')});
       $fields{'command'} = 'verify';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => "",
                      name => &mt('Verify Receipt'),
                      short_description => 
               &mt('')});
       #
       # Create the menu
       my $Str;
       # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>';
       $Str .= '<form method="post" action="" name="gradingMenu">';
       $Str .= '<input type="hidden" name="command" value="" />'.
       '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
    '<input type="hidden" name="probTitle"   value="'.$probTitle.'" ue="" />'."\n".
    '<input type="hidden" name="saveState"   value="" />'."\n".
    '<input type="hidden" name="gradingMenu" value="1" />'."\n".
    '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
       foreach my $menudata (@menu) {
           if ($menudata->{'name'} ne &mt('Verify Receipt')) {
               $Str .='    <h3><a '.
                   $menudata->{'jscript'}.
                   ' href="'.
                   $menudata->{'url'}.'" >'.
                   $menudata->{'name'}."</a></h3>\n";
           } else {
               $Str .='    <h3><input type="button" value="Verify Receipt" '.
                   $menudata->{'jscript'}.
                   ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
                   ' /></h3>';
               $Str .= ('&nbsp;'x8).
                       ' receipt: '.&Apache::lonnet::recprefix($env{'request.course.id'}).
                       '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />';
           }
           $Str .= '    '.('&nbsp;'x8).$menudata->{'short_description'}.
               "\n";
       }
       $Str .="</dl>\n";
       $Str .="</form>\n";
       $request->print(<<GRADINGMENUJS);
   <script type="text/javascript" language="javascript">
       function checkChoice(formname,val,cmdx) {
    if (val <= 2) {
       var cmd = radioSelection(formname.radioChoice);
       var cmdsave = cmd;
    } else {
       cmd = cmdx;
       cmdsave = 'submission';
    }
    formname.command.value = cmd;
    if (val < 5) formname.submit();
    if (val == 5) {
       if (!checkReceiptNo(formname,'notOK')) { 
           return false;
       } else {
           formname.submit();
       }
    }
       }
   
       function checkReceiptNo(formname,nospace) {
    var receiptNo = formname.receipt.value;
    var checkOpt = false;
    if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
    if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
    if (checkOpt) {
       alert("Please enter a receipt number given by a student in the receipt box.");
       formname.receipt.value = "";
       formname.receipt.focus();
       return false;
    }
    return true;
       }
   </script>
   GRADINGMENUJS
       &commonJSfunctions($request);
       return $Str;    
   }
   
   
   #--- Displays the submissions first page -------
   sub submit_options {
     my ($request) = @_;      my ($request) = @_;
     my ($symb)=&get_symb($request);      my ($symb)=&get_symb($request);
     if (!$symb) {return '';}      if (!$symb) {return '';}
Line 6009  GRADINGMENUJS Line 7370  GRADINGMENUJS
  '<input type="hidden" name="gradingMenu" value="1" />'."\n".   '<input type="hidden" name="gradingMenu" value="1" />'."\n".
  '<input type="hidden" name="showgrading" value="yes" />'."\n";   '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
     $result.='<table width="100%" border="0"><tr><td bgcolor=#777777>'."\n".      $result.='<table border="0"><tr><td bgcolor=#777777>'."\n".
  '<table width="100%" border="0"><tr bgcolor="#e6ffff"><td colspan="2">'."\n".   '<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n".
  '&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".   '&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".
  '<tr bgcolor="#ffffe6" valign="top"><td>'."\n";   '<tr bgcolor="#ffffe6" valign="top"><td>'."\n";
   
     $result.='<table width="100%" border="0">';      $result.='<table width="100%" border="0">';
       $result.='<tr bgcolor="#ffffe6" valign="top">'."\n";
       $result.='<td><b>'.&mt('Sections').'</b></td>';
       $result.='<td><b>'.&mt('Groups').'</b></td>';
       $result.='<td><b>'.&mt('Access Status').'</td>'."\n";
       $result.='<td><b>'.&mt('Submission Status').'</td>'."\n";
       $result.='</tr>';
     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".      $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".
  '&nbsp;'.&mt('Select Section').': <select name="section">'."\n";   '&nbsp;<select name="section" multiple="multiple" size="3">'."\n";
     if (ref($sections)) {      if (ref($sections)) {
  foreach (sort (@$sections)) {   foreach (sort (@$sections)) {
     $result.='<option value="'.$_.'" '.      $result.='<option value="'.$_.'" '.
Line 6024  GRADINGMENUJS Line 7391  GRADINGMENUJS
  }   }
     }      }
     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';      $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
       $result.= '</td><td>'."\n";
       $result.= &Apache::lonstatistics::GroupSelect('group','multiple',3);
       $result.='</td><td>'."\n";
       $result.=&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,3,undef,'mult');
   
     $result.=&mt('Student Status').':'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef);      $result.='</td>';
       $result.='<td><select name="submitonly" size="3">'.
     $result.='</td></tr>';  
   
     $result.='<tr bgcolor="#ffffe6"valign="top"><td><label>'.  
  '<input type="radio" name="radioChoice" value="submission" '.  
  ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').  
  '</label> <select name="submitonly">'.  
  '<option value="yes" '.   '<option value="yes" '.
  ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>'.   ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>'.
  '<option value="queued" '.   '<option value="queued" '.
Line 6042  GRADINGMENUJS Line 7407  GRADINGMENUJS
  '<option value="incorrect" '.   '<option value="incorrect" '.
  ($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>'.   ($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>'.
  '<option value="all" '.   '<option value="all" '.
  ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>'."\n";   ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>';
   
     $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.      $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3"><label>'.
    '<input type="radio" name="radioChoice" value="submission" '.
    ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').
    '</label> </td></tr>'."\n";
   
       $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3">'.
  '<label><input type="radio" name="radioChoice" value="viewgrades" '.   '<label><input type="radio" name="radioChoice" value="viewgrades" '.
  ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.   ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
  '<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n";   '<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n";
   
     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'.      $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'.
  '<label><input type="radio" name="radioChoice" value="pickStudentPage" '.   '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.
    '</td></tr>'."\n";
   
   
       $result.='<tr bgcolor="#ffffe6" valign="top"><td colspan="3">'.
    '<br /><label><input type="radio" name="radioChoice" value="pickStudentPage" '.
  ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.   ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
  'The <b>complete</b> set/page/sequence: For one student</label></td></tr>'."\n";   'The <b>complete</b> set/page/sequence/folder: For one student</label></td></tr>'."\n";
   
     $result.='<tr bgcolor="#ffffe6"><td><br />'.      $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'.
  '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.   '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.
  '</td></tr></table>'."\n";   '</td></tr></table>'."\n";
   
     $result.='</td><td valign="top">';      $result.='</td>'; #<td valign="top">';
   
     $result.='<table width="100%" border="0">';  
     $result.='<tr bgcolor="#ffffe6"><td>'.  
  '<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.  
  ' '.&mt('scores from file').' </td></tr>'."\n";  
   
     $result.='<tr bgcolor="#ffffe6"><td>'.  
         '<input type="button" onClick="javascript:checkChoice(this.form,\'6\',\'processclicker\');" value="'.&mt('Process').'" />'.  
         ' '.&mt('clicker file').' </td></tr>'."\n";  
   
     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.  
  '<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'.  
  '" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n";  
   
     if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) {  
  $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.  
     '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'.  
     ' '.&mt('receipt').': '.  
     &Apache::lonnet::recprefix($env{'request.course.id'}).  
     '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'.  
     '</td></tr>'."\n";  
     }   
     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.  
  '<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'.  
  '" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n";  
     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.  
  '<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'.  
  '" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n";  
   
     $result.='</table>'."\n".  #    $result.='<table width="100%" border="0">';
  '</td></tr></table>'."\n".  #    $result.='<tr bgcolor="#ffffe6"><td>'.
   # '<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.
   # ' '.&mt('scores from file').' </td></tr>'."\n";
   #
   #    $result.='<tr bgcolor="#ffffe6"><td>'.
   #        '<input type="button" onClick="javascript:checkChoice(this.form,\'6\',\'processclicker\');" value="'.&mt('Process').'" />'.
   #        ' '.&mt('clicker file').' </td></tr>'."\n";
   #
   #    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
   # '<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'.
   # '" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n";
   #
   #    if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) {
   # $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
   #    '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'.
   #    ' '.&mt('receipt').': '.
   #    &Apache::lonnet::recprefix($env{'request.course.id'}).
   #    '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'.
   #    '</td></tr>'."\n";
   #    } 
   #    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
   # '<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'.
   # '" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n";
   #    $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
   # '<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'.
   # '" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n";
   #
   #    $result.='</table>'."\n".'</td>';
       $result.= '</tr></table>'."\n".
  '</td></tr></table></form>'."\n";   '</td></tr></table></form>'."\n";
     return $result;      return $result;
 }  }
Line 6124  sub gather_clicker_ids { Line 7499  sub gather_clicker_ids {
     # Set up a couple variables.      # Set up a couple variables.
     my $username_idx = &Apache::loncoursedata::CL_SNAME();      my $username_idx = &Apache::loncoursedata::CL_SNAME();
     my $domain_idx   = &Apache::loncoursedata::CL_SDOM();      my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
       my $status_idx   = &Apache::loncoursedata::CL_STATUS();
   
     foreach my $student (keys(%$classlist)) {      foreach my $student (keys(%$classlist)) {
           if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
         my $username = $classlist->{$student}->[$username_idx];          my $username = $classlist->{$student}->[$username_idx];
         my $domain   = $classlist->{$student}->[$domain_idx];          my $domain   = $classlist->{$student}->[$domain_idx];
         my $clickers =          my $clickers =
     (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];      (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
         foreach my $id (split(/\,/,$clickers)) {          foreach my $id (split(/\,/,$clickers)) {
             $id=~s/^[\#0]+//;              $id=~s/^[\#0]+//;
               $id=~s/[\-\:]//g;
             if (exists($clicker_ids{$id})) {              if (exists($clicker_ids{$id})) {
  $clicker_ids{$id}.=','.$username.':'.$domain;   $clicker_ids{$id}.=','.$username.':'.$domain;
             } else {              } else {
Line 6155  sub gather_adv_clicker_ids { Line 7532  sub gather_adv_clicker_ids {
  (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];   (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
             foreach my $id (split(/\,/,$clickers)) {              foreach my $id (split(/\,/,$clickers)) {
  $id=~s/^[\#0]+//;   $id=~s/^[\#0]+//;
                   $id=~s/[\-\:]//g;
  if (exists($clicker_ids{$id})) {   if (exists($clicker_ids{$id})) {
     $clicker_ids{$id}.=','.$puname.':'.$pudom;      $clicker_ids{$id}.=','.$puname.':'.$pudom;
  } else {   } else {
Line 6211  sub process_clicker { Line 7589  sub process_clicker {
     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',
    ('iclicker' => 'i>clicker'));     ('iclicker' => 'i>clicker',
                                                       'interwrite' => 'interwrite PRS'));
     $symb = &Apache::lonenc::check_encrypt($symb);      $symb = &Apache::lonenc::check_encrypt($symb);
     $result.=<<ENDUPFORM;      $result.=<<ENDUPFORM;
 <script type="text/javascript">  <script type="text/javascript">
Line 6260  function sanitycheck() { Line 7639  function sanitycheck() {
 <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />  <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 <input type="file" name="upfile" size="50" />  <input type="file" name="upfile" size="50" />
 <br /><label>$type: $selectform</label>  <br /><label>$type: $selectform</label>
 <br /><label>$attendance: <input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" /></label>  <br /><label><input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" />$attendance </label>
 <br /><label>$personnel: <input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" /></label>  <br /><label><input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" />$personnel</label>
 <br /><label>$specific: <input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" /></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" />
 <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>
Line 6300  sub process_clicker_file { Line 7679  sub process_clicker_file {
    $correct_id=~tr/a-z/A-Z/;     $correct_id=~tr/a-z/A-Z/;
    $correct_id=~s/\s//gs;     $correct_id=~s/\s//gs;
    $correct_id=~s/^[\#0]+//;     $correct_id=~s/^[\#0]+//;
              $correct_id=~s/[\-\:]//g;
            if ($correct_id) {             if ($correct_id) {
       $correct_ids{$correct_id}='specified';        $correct_ids{$correct_id}='specified';
            }             }
Line 6359  ENDHEADER Line 7739  ENDHEADER
     if ($env{'form.upfiletype'} eq 'iclicker') {      if ($env{'form.upfiletype'} eq 'iclicker') {
  ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);   ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
     }      }
       if ($env{'form.upfiletype'} eq 'interwrite') {
           ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
       }
     $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.      $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
              '<input type="hidden" name="number" value="'.$number.'" />'.               '<input type="hidden" name="number" value="'.$number.'" />'.
                &mt('Awarding [_1] percent for corrion(s)',$number).'<br />'.
                '<input type="hidden" name="number" value="'.$number.'" />'.
              &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',               &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
                  $env{'form.pcorrect'},$env{'form.pincorrect'}).                   $env{'form.pcorrect'},$env{'form.pincorrect'}).
              '<br />';               '<br />';
Line 6380  ENDHEADER Line 7765  ENDHEADER
           $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';            $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
           $correct_count++;            $correct_count++;
        } elsif ($clicker_ids{$id}) {         } elsif ($clicker_ids{$id}) {
           $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';            if ($clicker_ids{$id}=~/\,/) {
           $student_count++;  # More than one user with the same clicker!
                $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
                $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                              "<select name='multi".$id."'>";
                foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
                    $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
                }
                $result.='</select>';
                $unknown_count++;
             } else {
   # Good: found one and only one user with the right clicker
                $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
                $student_count++;
             }
        } else {         } else {
           $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";            $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
           $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.            $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
Line 6401  ENDHEADER Line 7799  ENDHEADER
           $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';            $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
        }         }
     }      }
       if ($number<1) {
          $errormsg.="Found no questions.";
       }
     if ($errormsg) {      if ($errormsg) {
        $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';         $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
     } else {      } else {
Line 6438  sub iclicker_eval { Line 7839  sub iclicker_eval {
     return ($errormsg,$number);      return ($errormsg,$number);
 }  }
   
   sub interwrite_eval {
       my ($questiontitles,$responses)=@_;
       my $number=0;
       my $errormsg='';
       my $skipline=1;
       my $questionnumber=0;
       my %idresponses=();
       foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
           my %components=&Apache::loncommon::record_sep($line);
           my @entries=map {$components{$_}} (sort(keys(%components)));
           if ($entries[1] eq 'Time') { $skipline=0; next; }
           if ($entries[1] eq 'Response') { $skipline=1; }
           next if $skipline;
           if ($entries[0]!=$questionnumber) {
              $questionnumber=$entries[0];
              $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
              $number++;
           }
           my $id=$entries[4];
           $id=~s/^[\#0]+//;
           $id=~s/^v\d*\://i;
           $id=~s/[\-\:]//g;
           $idresponses{$id}[$number]=$entries[6];
       }
       foreach my $id (keys %idresponses) {
          $$responses{$id}=join(',',@{$idresponses{$id}});
          $$responses{$id}=~s/^\s*\,//;
       }
       return ($errormsg,$number);
   }
   
 sub assign_clicker_grades {  sub assign_clicker_grades {
     my ($r)=@_;      my ($r)=@_;
     my ($symb)=&get_symb($r);      my ($symb)=&get_symb($r);
Line 6490  ENDHEADER Line 7922  ENDHEADER
     my $pincorrect=$env{'form.pincorrect'};      my $pincorrect=$env{'form.pincorrect'};
     my $storecount=0;      my $storecount=0;
     foreach my $key (keys(%env)) {      foreach my $key (keys(%env)) {
          my $user='';
        if ($key=~/^form\.student\:(.*)$/) {         if ($key=~/^form\.student\:(.*)$/) {
           my $user=$1;            $user=$1;
          }
          if ($key=~/^form\.unknown\:(.*)$/) {
             my $id=$1;
             if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
                $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
             } elsif ($env{'form.multi'.$id}) {
                $user=$env{'form.multi'.$id};
             }
          }
          if ($user) { 
           my @answer=split(/\,/,$env{$key});            my @answer=split(/\,/,$env{$key});
           my $sum=0;            my $sum=0;
           for (my $i=0;$i<$number;$i++) {            for (my $i=0;$i<$number;$i++) {
Line 6533  ENDHEADER Line 7976  ENDHEADER
   
 sub handler {  sub handler {
     my $request=$_[0];      my $request=$_[0];
       &reset_caches();
     &reset_perm();  
     if ($env{'browser.mathml'}) {      if ($env{'browser.mathml'}) {
  &Apache::loncommon::content_type($request,'text/xml');   &Apache::loncommon::content_type($request,'text/xml');
     } else {      } else {
Line 6546  sub handler { Line 7988  sub handler {
     my $symb=&get_symb($request,1);      my $symb=&get_symb($request,1);
     my @commands=&Apache::loncommon::get_env_multiple('form.command');      my @commands=&Apache::loncommon::get_env_multiple('form.command');
     my $command=$commands[0];      my $command=$commands[0];
   
     if ($#commands > 0) {      if ($#commands > 0) {
  &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));   &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
     }      }
   
   
     $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 6589  sub handler { Line 8034  sub handler {
  } elsif ($command eq 'processGroup' && $perm{'vgr'}) {   } elsif ($command eq 'processGroup' && $perm{'vgr'}) {
     &processGroup($request);      &processGroup($request);
  } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {   } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
     $request->print(&gradingmenu($request));      $request->print(&grading_menu($request));
    } elsif ($command eq 'submit_options' && $perm{'vgr'}) {
       $request->print(&submit_options($request));
  } elsif ($command eq 'viewgrades' && $perm{'vgr'}) {   } elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
     $request->print(&viewgrades($request));      $request->print(&viewgrades($request));
  } elsif ($command eq 'handgrade' && $perm{'mgr'}) {   } elsif ($command eq 'handgrade' && $perm{'mgr'}) {
Line 6624  sub handler { Line 8071  sub handler {
  } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {   } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
     $request->print(&csvuploadassign($request));      $request->print(&csvuploadassign($request));
  } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {   } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
       &Apache::lonnet::logthis("Selecting pyhase");
     $request->print(&scantron_selectphase($request));      $request->print(&scantron_selectphase($request));
   } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {    } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
      $request->print(&scantron_do_warning($request));       $request->print(&scantron_do_warning($request));
Line 6647  sub handler { Line 8095  sub handler {
  }   }
     }      }
     $request->print(&Apache::loncommon::end_page());      $request->print(&Apache::loncommon::end_page());
       &reset_caches();
     return '';      return '';
 }  }
   

Removed from v.1.418  
changed lines
  Added in v.1.459


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