Diff for /loncom/homework/grades.pm between versions 1.465 and 1.766

version 1.465, 2007/10/26 00:27:55 version 1.766, 2020/05/07 18:43:52
Line 26 Line 26
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
   
   
   
 package Apache::grades;  package Apache::grades;
 use strict;  use strict;
 use Apache::style;  use Apache::style;
Line 38  use Apache::lonhomework; Line 40  use Apache::lonhomework;
 use Apache::lonpickcode;  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 :http);
 use Apache::lonlocal;  use Apache::lonlocal;
 use Apache::lonenc;  use Apache::lonenc;
   use Apache::lonstathelpers;
   use Apache::lonquickgrades;
   use Apache::bridgetask();
   use Apache::lontexconvert();
 use String::Similarity;  use String::Similarity;
   use HTML::Parser();
   use File::MMagic;
 use LONCAPA;  use LONCAPA;
   
 use POSIX qw(floor);  use POSIX qw(floor);
   
   
 my %perm=();  
 my %bubble_lines_per_response = ();     # no. bubble lines for each response.  
                                    # index is "symb.part_id"  
   
 my %first_bubble_line = (); # First bubble line no. for each bubble.  
   
 # Save and restore the bubble lines array to the form env.  
   
   
 sub save_bubble_lines {  
     foreach my $line (keys(%bubble_lines_per_response)) {  
  $env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};  
  $env{"form.scantron.first_bubble_line.$line"} =  
     $first_bubble_line{$line};  
     }  
 }  
   
   
 sub restore_bubble_lines {  
     my $line = 0;  
     %bubble_lines_per_response = ();  
     while ($env{"form.scantron.bubblelines.$line"}) {  
  my $value = $env{"form.scantron.bubblelines.$line"};  
  $bubble_lines_per_response{$line} = $value;  
  $first_bubble_line{$line}  =  
     $env{"form.scantron.first_bubble_line.$line"};  
  $line++;  
     }  
   
 }  
   
 #  Given the parsed scanline, get the response for   my %perm=();
 #  'answer' number n:  my %old_essays=();
   
 sub get_response_bubbles {  #  These variables are used to recover from ssi errors
     my ($parsed_line, $response)  = @_;  
   
   my $ssi_retries = 5;
   my $ssi_error;
   my $ssi_error_resource;
   my $ssi_error_message;
   
     my $bubble_line = $first_bubble_line{$response-1} +1;  
     my $bubble_lines= $bubble_lines_per_response{$response-1};  
       
     my $selected = "";  
   
     for (my $bline = 0; $bline < $bubble_lines; $bline++) {  sub ssi_with_retries {
  $selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":";      my ($resource, $retries, %form) = @_;
  $bubble_line++;      my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
       if ($response->is_error) {
    $ssi_error          = 1;
    $ssi_error_resource = $resource;
    $ssi_error_message  = $response->code . " " . $response->message;
     }      }
     return $selected;  
 }  
   
   
 # ----- 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 $content;
   
     return scalar(@matches);  
 }  }
   #
   #  Prodcuces an ssi retry failure error message to the user:
   #
   
   sub ssi_print_error {
 # Take a string known to have digits and convert all the      my ($r) = @_;
 # digits into letters in the range J,A..I.      my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
       $r->print('
 sub digits_to_letters {  <br />
     my ($input) = @_;  <h2>'.&mt('An unrecoverable network error occurred:').'</h2>
   <p>
     my @alphabet = ('J', 'A'..'I');  '.&mt('Unable to retrieve a resource from a server:').'<br />
   '.&mt('Resource:').' '.$ssi_error_resource.'<br />
     my @input    = split(//, $input);  '.&mt('Error:').' '.$ssi_error_message.'
     my $output ='';  </p>
     for (my $i = 0; $i < scalar(@input); $i++) {  <p>'.
  if ($input[$i] =~ /\d/) {  &mt('It is recommended that you try again later, as this error may mean the server was just temporarily unavailable, or is down for maintenance.').'<br />'.
     $output .= $alphabet[$input[$i]];  &mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
  } else {  '</p>');
     $output .= $input[$i];      return;
  }  
     }  
     return $output;  
 }  }
   
 #  #
 # --- Retrieve the parts from the metadata file.---  # --- Retrieve the parts from the metadata file.---
   # Returns an array of everything that the resources stores away
   #
   
 sub getpartlist {  sub getpartlist {
     my ($symb) = @_;      my ($symb,$errorref) = @_;
   
     my $navmap   = Apache::lonnavmaps::navmap->new();      my $navmap   = Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           if (ref($errorref)) { 
               $$errorref = 'navmap';
               return;
           }
       }
     my $res      = $navmap->getBySymb($symb);      my $res      = $navmap->getBySymb($symb);
     my $partlist = $res->parts();      my $partlist = $res->parts();
     my $url      = $res->src();      my $url      = $res->src();
     my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));      my $toolsymb;
       if ($url =~ /ext\.tool$/) {
           $toolsymb = $symb;
       }
       my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys',$toolsymb));
   
     my @stores;      my @stores;
     foreach my $part (@{ $partlist }) {      foreach my $part (@{ $partlist }) {
Line 151  sub getpartlist { Line 134  sub getpartlist {
     return @stores;      return @stores;
 }  }
   
 # --- Get the symbolic name of a problem and the url  
 sub get_symb {  
     my ($request,$silent) = @_;  
     (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;  
     my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));  
     if ($symb eq '') {   
  if (!$silent) {  
     $request->print("Unable to handle ambiguous references:$url:.");  
     return ();  
  }  
     }  
     &Apache::lonenc::check_decrypt(\$symb);  
     return ($symb);  
 }  
   
 #--- Format fullname, username:domain if different for display  #--- Format fullname, username:domain if different for display
 #--- Use anywhere where the student names are listed  #--- Use anywhere where the student names are listed
 sub nameUserString {  sub nameUserString {
     my ($type,$fullname,$uname,$udom) = @_;      my ($type,$fullname,$uname,$udom) = @_;
     if ($type eq 'header') {      if ($type eq 'header') {
  return '<b>&nbsp;Fullname&nbsp;</b><span class="LC_internal_info">(Username)</span>';   return '<b>&nbsp;'.&mt('Fullname').'&nbsp;</b><span class="LC_internal_info">('.&mt('Username').')</span>';
     } else {      } else {
  return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.   return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
     ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';      ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
Line 180  sub nameUserString { Line 148  sub nameUserString {
   
 #--- Get the partlist and the response type for a given problem. ---  #--- Get the partlist and the response type for a given problem. ---
 #--- Indicate if a response type is coded handgraded or not. ---  #--- Indicate if a response type is coded handgraded or not. ---
   #--- Sets response_error pointer to "1" if navmaps object broken ---
 sub response_type {  sub response_type {
     my ($symb) = shift;      my ($symb,$response_error) = @_;
   
     my $navmap = Apache::lonnavmaps::navmap->new();      my $navmap = Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           if (ref($response_error)) {
               $$response_error = 1;
           }
           return;
       }
     my $res = $navmap->getBySymb($symb);      my $res = $navmap->getBySymb($symb);
       unless (ref($res)) {
           $$response_error = 1;
           return;
       }
     my $partlist = $res->parts();      my $partlist = $res->parts();
     my %vPart =       my %vPart = 
  map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));   map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
Line 220  sub get_display_part { Line 199  sub get_display_part {
     my ($partID,$symb)=@_;      my ($partID,$symb)=@_;
     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);      my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
     if (defined($display) and $display ne '') {      if (defined($display) and $display ne '') {
  $display.= " (<span class=\"LC_internal_info\">id $partID</span>)";          $display.= ' (<span class="LC_internal_info">'
                     .&mt('Part ID: [_1]',$partID).'</span>)';
     } else {      } else {
  $display=$partID;   $display=$partID;
     }      }
     return $display;      return $display;
 }  }
   
 #--- Show resource title  
 #--- and parts and response type  
 sub showResourceInfo {  
     my ($symb,$probTitle,$checkboxes) = @_;  
     my $col=3;  
     if ($checkboxes) { $col=4; }  
     my $result = '<h3>'.&mt('Current Resource').': '.$probTitle.'</h3>'."\n";  
     $result .='<table border="0">';  
     my ($partlist,$handgrade,$responseType) = &response_type($symb);  
     my %resptype = ();  
     my $hdgrade='no';  
     my %partsseen;  
     foreach my $partID (sort keys(%$responseType)) {  
  foreach my $resID (sort keys(%{ $responseType->{$partID} })) {  
     my $handgrade=$$handgrade{$partID.'_'.$resID};  
     my $responsetype = $responseType->{$partID}->{$resID};  
     $hdgrade = $handgrade if ($handgrade eq 'yes');  
     $result.='<tr>';  
     if ($checkboxes) {  
  if (exists($partsseen{$partID})) {  
     $result.="<td>&nbsp;</td>";  
  } else {  
     $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='checked' /></td>";  
  }  
  $partsseen{$partID}=1;  
     }  
     my $display_part=&get_display_part($partID,$symb);  
     $result.='<td><b>Part: </b>'.$display_part.' <span class="LC_internal_info">'.  
  $resID.'</span></td>'.  
  '<td><b>Type: </b>'.$responsetype.'</td></tr>';  
 #    '<td><b>Handgrade: </b>'.$handgrade.'</td></tr>';  
  }  
     }  
     $result.='</table>'."\n";  
     return $result,$responseType,$hdgrade,$partlist,$handgrade;  
 }  
   
 sub reset_caches {  sub reset_caches {
     &reset_analyze_cache();      &reset_analyze_cache();
     &reset_perm();      &reset_perm();
       &reset_old_essays();
 }  }
   
 {  {
     my %analyze_cache;      my %analyze_cache;
       my %analyze_cache_formkeys;
   
     sub reset_analyze_cache {      sub reset_analyze_cache {
  undef(%analyze_cache);   undef(%analyze_cache);
           undef(%analyze_cache_formkeys);
     }      }
   
     sub get_analyze {      sub get_analyze {
  my ($symb,$uname,$udom)=@_;   my ($symb,$uname,$udom,$no_increment,$add_to_hash,$type,$trial,$rndseed,$bubbles_per_row)=@_;
  my $key = "$symb\0$uname\0$udom";   my $key = "$symb\0$uname\0$udom";
  return $analyze_cache{$key} if (exists($analyze_cache{$key}));          if ($type eq 'randomizetry') {
               if ($trial ne '') {
                   $key .= "\0".$trial;
               }
           }
    if (exists($analyze_cache{$key})) {
               my $getupdate = 0;
               if (ref($add_to_hash) eq 'HASH') {
                   foreach my $item (keys(%{$add_to_hash})) {
                       if (ref($analyze_cache_formkeys{$key}) eq 'HASH') {
                           if (!exists($analyze_cache_formkeys{$key}{$item})) {
                               $getupdate = 1;
                               last;
                           }
                       } else {
                           $getupdate = 1;
                       }
                   }
               }
               if (!$getupdate) {
                   return $analyze_cache{$key};
               }
           }
   
  my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);   my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
  $url=&Apache::lonnet::clutter($url);   $url=&Apache::lonnet::clutter($url);
  my $subresult=&Apache::lonnet::ssi($url,          my %form = ('grade_target'      => 'analyze',
    ('grade_target' => 'analyze'),                      'grade_domain'      => $udom,
    ('grade_domain' => $udom),                      'grade_symb'        => $symb,
    ('grade_symb' => $symb),                      'grade_courseid'    =>  $env{'request.course.id'},
    ('grade_courseid' =>                       'grade_username'    => $uname,
     $env{'request.course.id'}),                      'grade_noincrement' => $no_increment);
    ('grade_username' => $uname));          if ($bubbles_per_row ne '') {
               $form{'bubbles_per_row'} = $bubbles_per_row;
           }
           if ($type eq 'randomizetry') {
               $form{'grade_questiontype'} = $type;
               if ($rndseed ne '') {
                   $form{'grade_rndseed'} = $rndseed;
               }
           }
           if (ref($add_to_hash)) {
               %form = (%form,%{$add_to_hash});
           }
    my $subresult=&ssi_with_retries($url, $ssi_retries,%form);
  (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);   (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
  my %analyze=&Apache::lonnet::str2hash($subresult);   my %analyze=&Apache::lonnet::str2hash($subresult);
           if (ref($add_to_hash) eq 'HASH') {
               $analyze_cache_formkeys{$key} = $add_to_hash;
           } else {
               $analyze_cache_formkeys{$key} = {};
           }
  return $analyze_cache{$key} = \%analyze;   return $analyze_cache{$key} = \%analyze;
     }      }
   
     sub get_order {      sub get_order {
  my ($partid,$respid,$symb,$uname,$udom)=@_;   my ($partid,$respid,$symb,$uname,$udom,$no_increment,$type,$trial,$rndseed)=@_;
  my $analyze = &get_analyze($symb,$uname,$udom);   my $analyze = &get_analyze($symb,$uname,$udom,$no_increment,undef,$type,$trial,$rndseed);
  return $analyze->{"$partid.$respid.shown"};   return $analyze->{"$partid.$respid.shown"};
     }      }
   
     sub get_radiobutton_correct_foil {      sub get_radiobutton_correct_foil {
  my ($partid,$respid,$symb,$uname,$udom)=@_;   my ($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed)=@_;
  my $analyze = &get_analyze($symb,$uname,$udom);   my $analyze = &get_analyze($symb,$uname,$udom,undef,undef,$type,$trial,$rndseed);
  foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) {          my $foils = &get_order($partid,$respid,$symb,$uname,$udom,undef,$type,$trial,$rndseed);
     if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {          if (ref($foils) eq 'ARRAY') {
  return $foil;      foreach my $foil (@{$foils}) {
           if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
       return $foil;
           }
     }      }
  }   }
     }      }
   
       sub scantron_partids_tograde {
           my ($resource,$cid,$uname,$udom,$check_for_randomlist,$bubbles_per_row,$scancode) = @_;
           my (%analysis,@parts);
           if (ref($resource)) {
               my $symb = $resource->symb();
               my $add_to_form;
               if ($check_for_randomlist) {
                   $add_to_form = { 'check_parts_withrandomlist' => 1,};
               }
               if ($scancode) {
                   if (ref($add_to_form) eq 'HASH') {
                       $add_to_form->{'code_for_randomlist'} = $scancode;
                   } else {
                       $add_to_form = { 'code_for_randomlist' => $scancode,};
                   }
               }
               my $analyze = 
                   &get_analyze($symb,$uname,$udom,undef,$add_to_form,
                                undef,undef,undef,$bubbles_per_row);
               if (ref($analyze) eq 'HASH') {
                   %analysis = %{$analyze};
               }
               if (ref($analysis{'parts'}) eq 'ARRAY') {
                   foreach my $part (@{$analysis{'parts'}}) {
                       my ($id,$respid) = split(/\./,$part);
                       if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) {
                           push(@parts,$part);
                       }
                   }
               }
           }
           return (\%analysis,\@parts);
       }
   
 }  }
   
 #--- Clean response type for display  #--- Clean response type for display
Line 317  sub reset_caches { Line 340  sub reset_caches {
 #        response types only.  #        response types only.
 sub cleanRecord {  sub cleanRecord {
     my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,      my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
  $uname,$udom) = @_;   $uname,$udom,$type,$trial,$rndseed) = @_;
     my $grayFont = '<span class="LC_internal_info">';      my $grayFont = '<span class="LC_internal_info">';
     if ($response =~ /^(option|rank)$/) {      if ($response =~ /^(option|rank)$/) {
  my %answer=&Apache::lonnet::str2hash($answer);   my %answer=&Apache::lonnet::str2hash($answer);
           my @answer = %answer;
           %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
  my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});   my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
  my ($toprow,$bottomrow);   my ($toprow,$bottomrow);
  foreach my $foil (@$order) {   foreach my $foil (@$order) {
Line 332  sub cleanRecord { Line 357  sub cleanRecord {
     $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';      $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  }   }
  return '<blockquote><table border="1">'.   return '<blockquote><table border="1">'.
     '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.      '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
     $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';      $bottomrow.'</tr></table></blockquote>';
     } elsif ($response eq 'match') {      } elsif ($response eq 'match') {
  my %answer=&Apache::lonnet::str2hash($answer);   my %answer=&Apache::lonnet::str2hash($answer);
           my @answer = %answer;
           %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
  my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});   my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
  my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});   my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
  my ($toprow,$middlerow,$bottomrow);   my ($toprow,$middlerow,$bottomrow);
Line 352  sub cleanRecord { Line 379  sub cleanRecord {
     $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';      $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  }   }
  return '<blockquote><table border="1">'.   return '<blockquote><table border="1">'.
     '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.      '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Item ID</span></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
     $middlerow.'</tr>'.      $middlerow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
     $bottomrow.'</tr>'.'</table></blockquote>';      $bottomrow.'</tr></table></blockquote>';
     } elsif ($response eq 'radiobutton') {      } elsif ($response eq 'radiobutton') {
  my %answer=&Apache::lonnet::str2hash($answer);   my %answer=&Apache::lonnet::str2hash($answer);
           my @answer = %answer;
           %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
  my ($toprow,$bottomrow);   my ($toprow,$bottomrow);
  my $correct =    my $correct = 
     &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);      &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed);
  foreach my $foil (@$order) {   foreach my $foil (@$order) {
     if (exists($answer{$foil})) {      if (exists($answer{$foil})) {
  if ($foil eq $correct) {   if ($foil eq $correct) {
     $toprow.='<td><b>true</b></td>';      $toprow.='<td><b>'.&mt('true').'</b></td>';
  } else {   } else {
     $toprow.='<td><i>true</i></td>';      $toprow.='<td><i>'.&mt('true').'</i></td>';
  }   }
     } else {      } else {
  $toprow.='<td>false</td>';   $toprow.='<td>'.&mt('false').'</td>';
     }      }
     $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';      $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  }   }
  return '<blockquote><table border="1">'.   return '<blockquote><table border="1">'.
     '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.      '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
     $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';      $bottomrow.'</tr></table></blockquote>';
     } elsif ($response eq 'essay') {      } elsif ($response eq 'essay') {
  if (! exists ($env{'form.'.$symb})) {   if (! exists ($env{'form.'.$symb})) {
     my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',      my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
Line 391  sub cleanRecord { Line 420  sub cleanRecord {
     $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';      $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
     $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.      $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
  }   }
  $answer =~ s-\n-<br />-g;          $answer = &Apache::lontexconvert::msgtexconverted($answer);
  return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';   return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
   
     } elsif ( $response eq 'organic') {      } elsif ( $response eq 'organic') {
  my $result='Smile representation: "<tt>'.$answer.'</tt>"';          my $result=&mt('Smile representation: [_1]',
                              '"<tt>'.&HTML::Entities::encode($answer, '"<>&').'</tt>"');
  my $jme=$record->{$version."resource.$partid.$respid.molecule"};   my $jme=$record->{$version."resource.$partid.$respid.molecule"};
  $result.=&Apache::chemresponse::jme_img($jme,$answer,400);   $result.=&Apache::chemresponse::jme_img($jme,$answer,400);
  return $result;   return $result;
Line 428  sub cleanRecord { Line 459  sub cleanRecord {
     $result.='</ul>';      $result.='</ul>';
     return $result;      return $result;
  }   }
     } elsif ( $response =~ m/(?:numerical|formula)/) {      } elsif ( $response =~ m/(?:numerical|formula|custom)/) {
           # Respect multiple input fields, see Bug #5409
  $answer =    $answer = 
     &Apache::loncommon::format_previous_attempt_value('submission',      &Apache::loncommon::format_previous_attempt_value('submission',
       $answer);        $answer);
    return $answer;
     }      }
     return $answer;      return &HTML::Entities::encode($answer, '"<>&');
 }  }
   
 #-- A couple of common js functions  #-- A couple of common js functions
 sub commonJSfunctions {  sub commonJSfunctions {
     my $request = shift;      my $request = shift;
     $request->print(<<COMMONJSFUNCTIONS);      $request->print(&Apache::lonhtmlcommon::scripttag(<<COMMONJSFUNCTIONS));
 <script type="text/javascript" language="javascript">  
     function radioSelection(radioButton) {      function radioSelection(radioButton) {
  var selection=null;   var selection=null;
  if (radioButton.length > 1) {   if (radioButton.length > 1) {
Line 468  sub commonJSfunctions { Line 500  sub commonJSfunctions {
     return selectOne.value;      return selectOne.value;
  }   }
     }      }
 </script>  
 COMMONJSFUNCTIONS  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,$getgroup) = @_;      my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus) = @_;
     my @getsec;      my @getsec;
     my @getgroup;      my @getgroup;
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
Line 503  sub getclasslist { Line 534  sub getclasslist {
     #      #
     my %sections;      my %sections;
     my %fullnames;      my %fullnames;
       my ($cdom,$cnum,$partlist);
       if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
           $cdom = $env{"course.$env{'request.course.id'}.domain"};
           $cnum = $env{"course.$env{'request.course.id'}.num"};
           my $res_error;
           ($partlist,my $handgrade,my $responseType) = &response_type($symb,\$res_error);
       }
     foreach my $student (keys(%$classlist)) {      foreach my $student (keys(%$classlist)) {
         my $end      =           my $end      = 
             $classlist->{$student}->[&Apache::loncoursedata::CL_END()];              $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
Line 519  sub getclasslist { Line 557  sub getclasslist {
         my $group   =           my $group   = 
             $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];              $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  # filter students according to status selected   # filter students according to status selected
  if ($filterlist && (!($stu_status =~ /Any/))) {   if ($filterbyaccstatus && (!($stu_status =~ /Any/))) {
     if (!($stu_status =~ $status)) {      if (!($stu_status =~ $status)) {
  delete($classlist->{$student});   delete($classlist->{$student});
  next;   next;
Line 536  sub getclasslist { Line 574  sub getclasslist {
                }                  } 
         }          }
            if (($grp eq 'none') && !$group) {             if (($grp eq 'none') && !$group) {
                $exclude = 0;             $exclude = 0;
         }          }
     }      }
     if ($exclude) {      if ($exclude) {
         delete($classlist->{$student});          delete($classlist->{$student});
    next;
     }      }
  }   }
           if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
               my $udom =
                   $classlist->{$student}->[&Apache::loncoursedata::CL_SDOM()];
               my $uname =
                   $classlist->{$student}->[&Apache::loncoursedata::CL_SNAME()];
               if (($symb ne '') && ($udom ne '') && ($uname ne '')) {
                   if ($submitonly eq 'queued') {
                       my %queue_status =
                           &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
                                                                   $udom,$uname);
                       if (!defined($queue_status{'gradingqueue'})) {
                           delete($classlist->{$student});
                           next;
                       }
                   } else {
                       my (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
                       my $submitted = 0;
                       my $graded = 0;
                       my $incorrect = 0;
                       foreach (keys(%status)) {
                           $submitted = 1 if ($status{$_} ne 'nothing');
                           $graded = 1 if ($status{$_} =~ /^ungraded/);
                           $incorrect = 1 if ($status{$_} =~ /^incorrect/);
   
                           my ($foo,$partid,$foo1) = split(/\./,$_);
                           if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
                               $submitted = 0;
                           }
                       }
                       if (!$submitted && ($submitonly eq 'yes' ||
                                           $submitonly eq 'incorrect' ||
                                           $submitonly eq 'graded')) {
                           delete($classlist->{$student});
                           next;
                       } elsif (!$graded && ($submitonly eq 'graded')) {
                           delete($classlist->{$student});
                           next;
                       } elsif (!$incorrect && $submitonly eq 'incorrect') {
                           delete($classlist->{$student});
                           next;
                       }
                   }
               }
           }
  $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)) {
Line 557  sub getclasslist { Line 640  sub getclasslist {
     delete($classlist->{$student});      delete($classlist->{$student});
  }   }
     }      }
     my %seen = ();  
     my @sections = sort(keys(%sections));      my @sections = sort(keys(%sections));
     return ($classlist,\@sections,\%fullnames);      return ($classlist,\@sections,\%fullnames);
 }  }
Line 573  sub canmodify { Line 655  sub canmodify {
  #can modify the requested section   #can modify the requested section
  return 1;   return 1;
     } else {      } else {
  # can't modify the request section   # can't modify the requested section
  return 0;   return 0;
     }      }
  }   }
Line 586  sub canview { Line 668  sub canview {
     my ($sec)=@_;      my ($sec)=@_;
     if ($perm{'vgr'}) {      if ($perm{'vgr'}) {
  if (!defined($perm{'vgr_section'})) {   if (!defined($perm{'vgr_section'})) {
     # can modify whole class      # can view whole class
     return 1;      return 1;
  } else {   } else {
     if ($sec eq $perm{'vgr_section'}) {      if ($sec eq $perm{'vgr_section'}) {
  #can modify the requested section   #can view the requested section
  return 1;   return 1;
     } else {      } else {
  # can't modify the request section   # can't view the requested section
  return 0;   return 0;
     }      }
  }   }
     }      }
     #can't modify      #can't view
     return 0;      return 0;
 }  }
   
Line 623  sub student_gradeStatus { Line 705  sub student_gradeStatus {
 sub jscriptNform {  sub jscriptNform {
     my ($symb) = @_;      my ($symb) = @_;
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     my $jscript='<script type="text/javascript" language="javascript">'."\n".      my $jscript= &Apache::lonhtmlcommon::scripttag(
  '    function viewOneStudent(user,domain) {'."\n".   '    function viewOneStudent(user,domain) {'."\n".
  ' document.onestudent.student.value = user;'."\n".   ' document.onestudent.student.value = user;'."\n".
  ' document.onestudent.userdom.value = domain;'."\n".   ' document.onestudent.userdom.value = domain;'."\n".
  ' document.onestudent.submit();'."\n".   ' document.onestudent.submit();'."\n".
  '    }'."\n".   '    }'."\n".
  '</script>'."\n";   "\n");
     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".      $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\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'}.'" />'."\n".  
  '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".  
  '<input type="hidden" name="Status"  value="'.$stu_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".
Line 678  sub compute_points { Line 758  sub compute_points {
 #  #
   
 sub most_similar {  sub most_similar {
     my ($uname,$udom,$uessay,$old_essays)=@_;      my ($uname,$udom,$symb,$uessay)=@_;
   
       unless ($symb) { return ''; }
   
       unless (ref($old_essays{$symb}) eq 'HASH') { return ''; }
   
 # ignore spaces and punctuation  # ignore spaces and punctuation
   
Line 686  sub most_similar { Line 770  sub most_similar {
   
 # ignore empty submissions (occuring when only files are sent)  # ignore empty submissions (occuring when only files are sent)
   
     unless ($uessay=~/\w+/) { return ''; }      unless ($uessay=~/\w+/s) { return ''; }
   
 # these will be returned. Do not care if not at least 50 percent similar  # these will be returned. Do not care if not at least 50 percent similar
     my $limit=0.6;      my $limit=0.6;
Line 695  sub most_similar { Line 779  sub most_similar {
     my $scrsid='';      my $scrsid='';
     my $sessay='';      my $sessay='';
 # go through all essays ...  # go through all essays ...
     foreach my $tkey (keys(%$old_essays)) {      foreach my $tkey (keys(%{$old_essays{$symb}})) {
  my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));   my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
 # ... except the same student  # ... except the same student
         next if (($tname eq $uname) && ($tdom eq $udom));          next if (($tname eq $uname) && ($tdom eq $udom));
  my $tessay=$old_essays->{$tkey};   my $tessay=$old_essays{$symb}{$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);
Line 709  sub most_similar { Line 793  sub most_similar {
     $sname=$tname;      $sname=$tname;
     $sdom=$tdom;      $sdom=$tdom;
     $scrsid=$tcrsid;      $scrsid=$tcrsid;
     $sessay=$old_essays->{$tkey};      $sessay=$old_essays{$symb}{$tkey};
  }   }
     }      }
     if ($limit>0.6) {      if ($limit>0.6) {
Line 723  sub most_similar { Line 807  sub most_similar {
   
 #------------------------------------ Receipt Verification Routines  #------------------------------------ Receipt Verification Routines
 #  #
   
   sub initialverifyreceipt {
      my ($request,$symb) = @_;
      &commonJSfunctions($request);
      return '<form name="gradingMenu" action=""><input type="submit" value="'.&mt('Verify Receipt Number.').'" />'.
           &Apache::lonnet::recprefix($env{'request.course.id'}).
           '-<input type="text" name="receipt" size="4" />'.
           '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
           '<input type="hidden" name="command" value="verify" />'.
           "</form>\n";
   }
   
 #--- Check whether a receipt number is valid.---  #--- Check whether a receipt number is valid.---
 sub verifyreceipt {  sub verifyreceipt {
     my $request  = shift;      my ($request,$symb) = @_;
   
     my $courseid = $env{'request.course.id'};      my $courseid = $env{'request.course.id'};
     my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.      my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
  $env{'form.receipt'};   $env{'form.receipt'};
     $receipt     =~ s/[^\-\d]//g;      $receipt     =~ s/[^\-\d]//g;
     my ($symb)   = &get_symb($request);  
   
     my $title.='<h3><span class="LC_info">Verifying Submission Receipt '.      my $title =
  $receipt.'</h3></span>'."\n".   '<h3><span class="LC_info">'.
  '<h4><b>Resource: </b>'.$env{'form.probTitle'}.'</h4><br /><br />'."\n";   &mt('Verifying Receipt Number [_1]',$receipt).
    '</span></h3>'."\n";
   
     my ($string,$contents,$matches) = ('','',0);      my ($string,$contents,$matches) = ('','',0);
     my (undef,undef,$fullname) = &getclasslist('all','0');      my (undef,undef,$fullname) = &getclasslist('all','0');
Line 744  sub verifyreceipt { Line 840  sub verifyreceipt {
     if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||      if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
  $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }   $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
     my $parts=['0'];      my $parts=['0'];
     if ($receiptparts) { ($parts)=&response_type($symb); }      if ($receiptparts) {
           my $res_error; 
           ($parts)=&response_type($symb,\$res_error);
           if ($res_error) {
               return &navmap_errormsg();
           } 
       }
       
       my $header = 
    &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th>&nbsp;'.&mt('Fullname').'&nbsp;</th>'."\n".
    '<th>&nbsp;'.&mt('Username').'&nbsp;</th>'."\n".
    '<th>&nbsp;'.&mt('Domain').'&nbsp;</th>';
       if ($receiptparts) {
    $header.='<th>&nbsp;'.&mt('Problem Part').'&nbsp;</th>';
       }
       $header.=
    &Apache::loncommon::end_data_table_header_row();
   
     foreach (sort       foreach (sort 
      {       {
  if (lc($$fullname{$a}) ne lc($$fullname{$b})) {   if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
Line 755  sub verifyreceipt { Line 870  sub verifyreceipt {
  my ($uname,$udom)=split(/\:/);   my ($uname,$udom)=split(/\:/);
  foreach my $part (@$parts) {   foreach my $part (@$parts) {
     if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {      if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
  $contents.='<tr bgcolor="#ffffe6"><td>&nbsp;'."\n".   $contents.=
       &Apache::loncommon::start_data_table_row().
       '<td>&nbsp;'."\n".
     '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.      '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
     '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".      '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
     '<td>&nbsp;'.$uname.'&nbsp;</td>'.      '<td>&nbsp;'.$uname.'&nbsp;</td>'.
Line 763  sub verifyreceipt { Line 880  sub verifyreceipt {
  if ($receiptparts) {   if ($receiptparts) {
     $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';      $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
  }   }
  $contents.='</tr>'."\n";   $contents.= 
       &Apache::loncommon::end_data_table_row()."\n";
   
  $matches++;   $matches++;
     }      }
  }   }
     }      }
     if ($matches == 0) {      if ($matches == 0) {
  $string = $title.'No match found for the above receipt.';          $string = $title
                    .'<p class="LC_warning">'
                    .&mt('No match found for the above receipt number.')
                    .'</p>';
     } else {      } else {
  $string = &jscriptNform($symb).$title.   $string = &jscriptNform($symb).$title.
     'The above receipt matches the following student'.      '<p>'.
     ($matches <= 1 ? '.' : 's.')."\n".      &mt('The above receipt number matches the following [quant,_1,student].',$matches).
     '<table border="0"><tr><td bgcolor="#777777">'."\n".      '</p>'.
     '<table border="0"><tr bgcolor="#e6ffff">'."\n".      $header.
     '<td><b>&nbsp;Fullname&nbsp;</b></td>'."\n".      $contents.
     '<td><b>&nbsp;Username&nbsp;</b></td>'."\n".      &Apache::loncommon::end_data_table()."\n";
     '<td><b>&nbsp;Domain&nbsp;</b></td>';  
  if ($receiptparts) {  
     $string.='<td>&nbsp;Problem Part&nbsp;</td>';  
  }  
  $string.='</tr>'."\n".$contents.  
     '</table></td></tr></table>'."\n";  
     }      }
     return $string.&show_grading_menu_form($symb);      return $string;
 }  }
   
 #--- This is called by a number of programs.  #--- This is called by a number of programs.
Line 794  sub verifyreceipt { Line 909  sub verifyreceipt {
 #--- Also called directly when one clicks on the subm button   #--- Also called directly when one clicks on the subm button 
 #    on the problem page.  #    on the problem page.
 sub listStudents {  sub listStudents {
     my ($request) = shift;      my ($request,$symb,$submitonly) = @_;
   
     my ($symb) = &get_symb($request);      my $is_tool   = ($symb =~ /ext\.tool$/);
     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 $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
     my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};      unless ($submitonly) {
     my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';          $submitonly = $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
     $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ?       }
  &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};  
   
     my $result='<h3><span class="LC_info">&nbsp;'.$viewgrade.  
  ' Submissions for a Student or a Group of Students</span></h3>';  
   
     my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));  
   
     $request->print(<<LISTJAVASCRIPT);      my $result='';
 <script type="text/javascript" language="javascript">      my $res_error;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
   
       my %js_lt = &Apache::lonlocal::texthash (
    'multiple' => 'Please select a student or group of students before clicking on the Next button.',
    'single'   => 'Please select the student before clicking on the Next button.',
        );
       &js_escape(\%js_lt);
       $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
     function checkSelect(checkBox) {      function checkSelect(checkBox) {
  var ctr=0;   var ctr=0;
  var sense="";   var sense="";
Line 822  sub listStudents { Line 939  sub listStudents {
     ctr++;      ctr++;
  }   }
     }      }
     sense = "a student or group of students";      sense = '$js_lt{'multiple'}';
  } else {   } else {
     if (checkBox.checked) {      if (checkBox.checked) {
  ctr = 1;   ctr = 1;
     }      }
     sense = "the student";      sense = '$js_lt{'single'}';
  }   }
  if (ctr == 0) {   if (ctr == 0) {
     alert("Please select "+sense+" before clicking on the Next button.");      alert(sense);
     return false;      return false;
  }   }
  document.gradesub.submit();   document.gradesub.submit();
Line 841  sub listStudents { Line 958  sub listStudents {
  formname.command.value = 'submission';   formname.command.value = 'submission';
  formname.submit();   formname.submit();
     }      }
 </script>  
 LISTJAVASCRIPT  LISTJAVASCRIPT
   
     &commonJSfunctions($request);      &commonJSfunctions($request);
     $request->print($result);      $request->print($result);
   
     my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';  
     my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';  
     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.      my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
  "\n".$table.   "\n";
  '&nbsp;<b>View Problem Text: </b><label><input type="radio" name="vProb" value="no" checked="checked" /> no </label>'."\n".  
  '<label><input type="radio" name="vProb" value="yes" /> one student </label>'."\n".      $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
  '<label><input type="radio" name="vProb" value="all" /> all students </label><br />'."\n".      unless ($is_tool) {
  '&nbsp;<b>View Answer: </b><label><input type="radio" name="vAns" value="no"  /> no </label>'."\n".          $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
  '<label><input type="radio" name="vAns" value="yes" /> one student </label>'."\n".                        .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
  '<label><input type="radio" name="vAns" value="all" checked="checked" /> all students </label><br />'."\n".                        .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
  '&nbsp;<b>Submissions: </b>'."\n";                        .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {                        .&Apache::lonhtmlcommon::row_closure();
  $gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n";          $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
                         .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
                         .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
                         .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
                         .&Apache::lonhtmlcommon::row_closure();
     }      }
   
       my $submission_options;
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;      my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
     $env{'form.Status'} = $saveStatus;      $env{'form.Status'} = $saveStatus;
     $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n".      my %optiontext;
  '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n".      if ($is_tool) {
  '<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n".          %optiontext = &Apache::lonlocal::texthash (
  '<label><input type="radio" name="lastSub" value="all" /> all details</label><br />'."\n".                            lastonly => 'last transaction',
         '&nbsp;<b>Grading Increments:</b> <select name="increment">'.                            last     => 'last transaction with details',
         '<option value="1">Whole Points</option>'.                            datesub  => 'all transactions',
         '<option value=".5">Half Points</option>'.                            all      => 'all transactions with details',
         '<option value=".25">Quarter Points</option>'.                        );
         '<option value=".1">Tenths of a Point</option>'.      } else {
         '</select>'.          %optiontext = &Apache::lonlocal::texthash (
                             lastonly => 'last submission',
                             last     => 'last submission with details',
                             datesub  => 'all submissions',
                             all      => 'all submissions with details',
                         );
       }
       $submission_options.=
           '<span class="LC_nobreak">'.
           '<label><input type="radio" name="lastSub" value="lastonly" /> '.
           $optiontext{'lastonly'}.' </label></span>'."\n".
           '<span class="LC_nobreak">'.
           '<label><input type="radio" name="lastSub" value="last" /> '.
           $optiontext{'last'}.' </label></span>'."\n".
           '<span class="LC_nobreak">'.
           '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.
           $optiontext{'datesub'}.'</label></span>'."\n".
           '<span class="LC_nobreak">'.
           '<label><input type="radio" name="lastSub" value="all" /> '.
           $optiontext{'all'}.'</label></span>';
       my $viewtitle;
       if ($is_tool) {
           $viewtitle = &mt('View Transactions');
       } else {
           $viewtitle = &mt('View Submissions');
       }
       $gradeTable .= &Apache::lonhtmlcommon::row_title($viewtitle)
                     .$submission_options
                     .&Apache::lonhtmlcommon::row_closure();
   
       my $closure;
       if (($is_tool) && (exists($env{'form.Status'}))) {
           $closure = 1;
       }
       $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments'))
                     .'<select name="increment">'
                     .'<option value="1">'.&mt('Whole Points').'</option>'
                     .'<option value=".5">'.&mt('Half Points').'</option>'
                     .'<option value=".25">'.&mt('Quarter Points').'</option>'
                     .'<option value=".1">'.&mt('Tenths of a Point').'</option>'
                     .'</select>'
                     .&Apache::lonhtmlcommon::row_closure($closure);
   
       $gradeTable .= 
         &build_section_inputs().          &build_section_inputs().
  '<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".   '<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
  '<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".  
  '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\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="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".   '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<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.Status'})) {
  $gradeTable.='<input type="hidden" name="Status"   value="'.$stu_status.'" />'."\n";   $gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
       } else {
           if ($is_tool) {
               $closure = 1;
           }
           $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status'))
                         .&Apache::lonhtmlcommon::StatusOptions(
                              $saveStatus,undef,1,'javascript:reLoadList(this.form);')
                         .&Apache::lonhtmlcommon::row_closure($closure);
       }
   
       unless ($is_tool) {
           $closure = 1;
           $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
                         .'<input type="checkbox" name="checkPlag" checked="checked" />'
                         .&Apache::lonhtmlcommon::row_closure($closure);
       }
       $gradeTable .= &Apache::lonhtmlcommon::end_pick_box();
       my $regrademsg;
       if ($is_tool) {
           $regrademsg =&mt("To view/grade/regrade, click on the check box(es) next to the student's name(s). Then click on the Next button.");
     } else {      } else {
  $gradeTable.='<b>Student Status:</b> '.          $regrademsg = &mt("To view/grade/regrade a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.");
     &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';  
     }      }
       $gradeTable .= '<p>'
     $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '.                    .$regrademsg."\n"
  'next to the student\'s name(s). Then click on the Next button.<br />'."\n".                    .'<input type="hidden" name="command" value="processGroup" />'
  '<input type="hidden" name="command" value="processGroup" />'."\n";                    .'</p>';
   
 # checkall buttons  # checkall buttons
     $gradeTable.=&check_script('gradesub', 'stuinfo');      $gradeTable.=&check_script('gradesub', 'stuinfo');
     $gradeTable.='<input type="button" '."\n".      $gradeTable.='<input type="button" '."\n".
  'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".          'onclick="javascript:checkSelect(this.form.stuinfo);" '."\n".
  'value="Next->" /> <br />'."\n";          'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
     $gradeTable.=&check_buttons();      $gradeTable.=&check_buttons();
     $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />Check For Plagiarism</label>';  
     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);      my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
     $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.      $gradeTable.= &Apache::loncommon::start_data_table().
  '<table border="0"><tr bgcolor="#e6ffff">';   &Apache::loncommon::start_data_table_header_row();
     my $loop = 0;      my $loop = 0;
     while ($loop < 2) {      while ($loop < 2) {
  $gradeTable.='<td><b>&nbsp;No.</b>&nbsp;</td><td><b>&nbsp;Select&nbsp;</b></td>'.   $gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
     '<td>'.&nameUserString('header').'&nbsp;Section/Group</td>';      '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
  if ($env{'form.showgrading'} eq 'yes'    if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
     && $submitonly ne 'queued'      foreach my $part (sort(@$partlist)) {
     && $submitonly ne 'all') {   my $display_part=
     foreach (sort(@$partlist)) {      &get_display_part((split(/_/,$part))[0],$symb);
  my $display_part=&get_display_part((split(/_/))[0],$symb);   $gradeTable.=
  $gradeTable.='<td><b>&nbsp;Part: '.$display_part.      '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
     ' Status&nbsp;</b></td>';  
     }      }
  } elsif ($submitonly eq 'queued') {   } elsif ($submitonly eq 'queued') {
     $gradeTable.='<td><b>&nbsp;'.&mt('Queue Status').'&nbsp;</b></td>';      $gradeTable.='<th>'.&mt('Queue Status').'&nbsp;</th>';
  }   }
  $loop++;   $loop++;
 # $gradeTable.='<td></td>' if ($loop%2 ==1);  # $gradeTable.='<td></td>' if ($loop%2 ==1);
     }      }
     $gradeTable.='</tr>'."\n";      $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n";
   
     my $ctr = 0;      my $ctr = 0;
     foreach my $student (sort       foreach my $student (sort 
Line 945  LISTJAVASCRIPT Line 1121  LISTJAVASCRIPT
     $status{'gradingqueue'} = $queue_status{'gradingqueue'};      $status{'gradingqueue'} = $queue_status{'gradingqueue'};
  }   }
   
  if ($env{'form.showgrading'} eq 'yes'    if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
     && $submitonly ne 'queued'  
     && $submitonly ne 'all') {  
     (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);      (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
     my $submitted = 0;      my $submitted = 0;
     my $graded = 0;      my $graded = 0;
Line 978  LISTJAVASCRIPT Line 1152  LISTJAVASCRIPT
  my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];   my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
         my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];          my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  if ( $perm{'vgr'} eq 'F' ) {   if ( $perm{'vgr'} eq 'F' ) {
     $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);      if ($ctr%2 ==1) {
    $gradeTable.= &Apache::loncommon::start_data_table_row();
       }
     $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.      $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
                '<td align="center"><label><input type=checkbox name="stuinfo" value="'.                 '<td align="center"><label><input type="checkbox" name="stuinfo" value="'.
                $student.':'.$$fullname{$student}.':::SECTION'.$section.                 $student.':'.$$fullname{$student}.':::SECTION'.$section.
        ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.         ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
        &nameUserString(undef,$$fullname{$student},$uname,$udom).         &nameUserString(undef,$$fullname{$student},$uname,$udom).
        '&nbsp;'.$section.'/'.$group.'</td>'."\n";         '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
   
     if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {      if ($submitonly ne 'all') {
  foreach (sort keys(%status)) {   foreach (sort(keys(%status))) {
     next if (/^resource.*?submitted_by$/);      next if ($_ =~ /^resource.*?submitted_by$/);
     $gradeTable.='<td align="center">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";      $gradeTable.='<td align="center">&nbsp;'.&mt($status{$_}).'&nbsp;</td>'."\n";
  }   }
     }      }
 #    $gradeTable.='<td></td>' if ($ctr%2 ==1);  #    $gradeTable.='<td></td>' if ($ctr%2 ==1);
     $gradeTable.='</tr>'."\n" if ($ctr%2 ==0);      if ($ctr%2 ==0) {
    $gradeTable.=&Apache::loncommon::end_data_table_row()."\n";
       }
  }   }
     }      }
     if ($ctr%2 ==1) {      if ($ctr%2 ==1) {
  $gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';   $gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
     if ($env{'form.showgrading'} eq 'yes'       if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
  && $submitonly ne 'queued'  
  && $submitonly ne 'all') {  
  foreach (@$partlist) {   foreach (@$partlist) {
     $gradeTable.='<td>&nbsp;</td>';      $gradeTable.='<td>&nbsp;</td>';
  }   }
     } elsif ($submitonly eq 'queued') {      } elsif ($submitonly eq 'queued') {
  $gradeTable.='<td>&nbsp;</td>';   $gradeTable.='<td>&nbsp;</td>';
     }      }
  $gradeTable.='</tr>';   $gradeTable.=&Apache::loncommon::end_data_table_row();
     }      }
   
     $gradeTable.='</table></td></tr></table>'."\n".      $gradeTable.=&Apache::loncommon::end_data_table()."\n".
  '<input type="button" '.          '<input type="button" '.
  'onClick="javascript:checkSelect(this.form.stuinfo);" '.          'onclick="javascript:checkSelect(this.form.stuinfo);" '.
  'value="Next->" /></form>'."\n";          'value="'.&mt('Next').' &rarr;" /></form>'."\n";
     if ($ctr == 0) {      if ($ctr == 0) {
  my $num_students=(scalar(keys(%$fullname)));   my $num_students=(scalar(keys(%$fullname)));
  if ($num_students eq 0) {   if ($num_students eq 0) {
     $gradeTable='<br />&nbsp;<span class="LC_warning">There are no students currently enrolled.</span>';      $gradeTable='<br />&nbsp;<span class="LC_warning">'.&mt('There are no students currently enrolled.').'</span>';
  } else {   } else {
     my $submissions='submissions';      my $submissions='submissions';
     if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }      if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
     if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }      if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
     if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }      if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
     $gradeTable='<br />&nbsp;<span class="LC_warning">'.      $gradeTable='<br />&nbsp;<span class="LC_warning">'.
  'No '.$submissions.' found for this resource for any students. ('.$num_students.   &mt('No '.$submissions.' found for this resource for any students. ([quant,_1,student] checked for '.$submissions.')',
  ' students checked for '.$submissions.')</span><br />';      $num_students).
    '</span><br />';
  }   }
     } elsif ($ctr == 1) {      } elsif ($ctr == 1) {
  $gradeTable =~ s/type=checkbox/type=checkbox checked/;   $gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/;
     }      }
     $gradeTable.=&show_grading_menu_form($symb);  
     $request->print($gradeTable);      $request->print($gradeTable);
     return '';      return '';
 }  }
Line 1038  LISTJAVASCRIPT Line 1214  LISTJAVASCRIPT
 #---- Called from the listStudents routine  #---- Called from the listStudents routine
   
 sub check_script {  sub check_script {
     my ($form, $type)=@_;      my ($form,$type) = @_;
     my $chkallscript='<script type="text/javascript">      my $chkallscript = &Apache::lonhtmlcommon::scripttag('
     function checkall() {      function checkall() {
         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {          for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
             ele = document.forms.'.$form.'.elements[i];              ele = document.forms.'.$form.'.elements[i];
Line 1070  sub check_script { Line 1246  sub check_script {
         }          }
     }      }
   
 </script>'."\n";  '."\n");
     return $chkallscript;      return $chkallscript;
 }  }
   
 sub check_buttons {  sub check_buttons {
     my $buttons.='<input type="button" onclick="checkall()" value="Check All" />';      my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
     $buttons.='<input type="button" onclick="uncheckall()" value="Uncheck All" />&nbsp;';      $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" />&nbsp;';
     $buttons.='<input type="button" onclick="checksec()" value="Check Section/Group" />';      $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
     $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';      $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
     return $buttons;      return $buttons;
 }  }
   
 #     Displays the submissions for one student or a group of students  #     Displays the submissions for one student or a group of students
 sub processGroup {  sub processGroup {
     my ($request)  = shift;      my ($request,$symb) = @_;
     my $ctr        = 0;      my $ctr        = 0;
     my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');      my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
     my $total      = scalar(@stuchecked)-1;      my $total      = scalar(@stuchecked)-1;
Line 1094  sub processGroup { Line 1270  sub processGroup {
  $env{'form.student'}        = $uname;   $env{'form.student'}        = $uname;
  $env{'form.userdom'}        = $udom;   $env{'form.userdom'}        = $udom;
  $env{'form.fullname'}       = $fullname;   $env{'form.fullname'}       = $fullname;
  &submission($request,$ctr,$total);   &submission($request,$ctr,$total,$symb);
  $ctr++;   $ctr++;
     }      }
     return '';      return '';
Line 1108  sub processGroup { Line 1284  sub processGroup {
 #--- Javascript to handle the submission page functionality ---  #--- Javascript to handle the submission page functionality ---
 sub sub_page_js {  sub sub_page_js {
     my $request = shift;      my $request = shift;
     $request->print(<<SUBJAVASCRIPT);      my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
 <script type="text/javascript" language="javascript">      &js_escape(\$alertmsg);
       $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
     function updateRadio(formname,id,weight) {      function updateRadio(formname,id,weight) {
  var gradeBox = formname["GD_BOX"+id];   var gradeBox = formname["GD_BOX"+id];
  var radioButton = formname["RADVAL"+id];   var radioButton = formname["RADVAL"+id];
Line 1118  sub sub_page_js { Line 1295  sub sub_page_js {
  gradeBox.value = pts;   gradeBox.value = pts;
  var resetbox = false;   var resetbox = false;
  if (isNaN(pts) || pts < 0) {   if (isNaN(pts) || pts < 0) {
     alert("A number equal or greater than 0 is expected. Entered value = "+pts);      alert("$alertmsg"+pts);
     for (var i=0; i<radioButton.length; i++) {      for (var i=0; i<radioButton.length; i++) {
  if (radioButton[i].checked) {   if (radioButton[i].checked) {
     gradeBox.value = i;      gradeBox.value = i;
Line 1226  sub sub_page_js { Line 1403  sub sub_page_js {
     }      }
  }   }
     }      }
       
  }   }
     }      }
       
  }  
  if (val == "Grade Student") {  
     formname.showgrading.value = "yes";  
     if (formname.Status.value == "") {  
  formname.Status.value = "Active";  
     }  
     formname.studentNo.value = total;  
  }   }
  formname.submit();   formname.submit();
     }      }
Line 1277  sub sub_page_js { Line 1445  sub sub_page_js {
   
  formname.submit();   formname.submit();
     }      }
 </script>  
 SUBJAVASCRIPT  SUBJAVASCRIPT
 }  }
   
Line 1287  sub sub_page_kw_js { Line 1454  sub sub_page_kw_js {
     my $iconpath = $request->dir_config('lonIconsURL');      my $iconpath = $request->dir_config('lonIconsURL');
     &commonJSfunctions($request);      &commonJSfunctions($request);
   
     my $inner_js_msg_central=<<INNERJS;      my $inner_js_msg_central= (<<INNERJS);
     <script text="text/javascript">  <script type="text/javascript">
     function checkInput() {      function checkInput() {
       opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);        opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
       var nmsg   = opener.document.SCORE.savemsgN.value;        var nmsg   = opener.document.SCORE.savemsgN.value;
Line 1325  sub sub_page_kw_js { Line 1492  sub sub_page_kw_js {
       self.close()        self.close()
   
     }      }
     </script>  </script>
 INNERJS  INNERJS
   
     my $inner_js_highlight_central=<<INNERJS;      my $inner_js_highlight_central= (<<INNERJS);
  <script type="text/javascript">  <script type="text/javascript">
     function updateChoice(flag) {      function updateChoice(flag) {
       opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);        opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
       opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);        opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
Line 1363  INNERJS Line 1530  INNERJS
   
     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();      my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
     $docopen=~s/^document\.//;      $docopen=~s/^document\.//;
     $request->print(<<SUBJAVASCRIPT);      my %js_lt = &Apache::lonlocal::texthash(
 <script type="text/javascript" language="javascript">                  keyw => 'Keywords list, separated by a space. Add/delete to list if desired.',
                   plse => 'Please select a word or group of words from document and then click this link.',
                   adds => 'Add selection to keyword list? Edit if desired.',
                   col1 => 'red',
                   col2 => 'green',
                   col3 => 'blue',
                   siz1 => 'normal',
                   siz2 => '+1',
                   siz3 => '+2',
                   sty1 => 'normal',
                   sty2 => 'italic',
                   sty3 => 'bold',
                );
       my %html_js_lt = &Apache::lonlocal::texthash(
                   comp => 'Compose Message for: ',
                   incl => 'Include',
                   type => 'Type',
                   subj => 'Subject',
                   mesa => 'Message',
                   new  => 'New',
                   save => 'Save',
                   canc => 'Cancel',
                   kehi => 'Keyword Highlight Options',
                   txtc => 'Text Color',
                   font => 'Font Size',
                   fnst => 'Font Style',
                );
       &js_escape(\%js_lt);
       &html_escape(\%html_js_lt);
       &js_escape(\%html_js_lt);
       $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
   
 //===================== Show list of keywords ====================  //===================== Show list of keywords ====================
   function keywords(formname) {    function keywords(formname) {
     var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value);      var nret = prompt("$js_lt{'keyw'}",formname.keywords.value);
     if (nret==null) return;      if (nret==null) return;
     formname.keywords.value = nret;      formname.keywords.value = nret;
   
Line 1395  INNERJS Line 1592  INNERJS
     else return;      else return;
     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");      var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
     if (cleantxt=="") {      if (cleantxt=="") {
  alert("Please select a word or group of words from document and then click this link.");   alert("$js_lt{'plse'}");
  return;   return;
     }      }
     var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);      var nret = prompt("$js_lt{'adds'}",cleantxt);
     if (nret==null) return;      if (nret==null) return;
     document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;      document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
     if (document.SCORE.keywords.value != "") {      if (document.SCORE.keywords.value != "") {
Line 1462  INNERJS Line 1659  INNERJS
   
   function savedMsgHeader(Nmsg,usrctr,fullname) {    function savedMsgHeader(Nmsg,usrctr,fullname) {
     var height = 70*Nmsg+250;      var height = 70*Nmsg+250;
     var scrollbar = "no";  
     if (height > 600) {      if (height > 600) {
  height = 600;   height = 600;
  scrollbar = "yes";  
     }      }
     var xpos = (screen.width-600)/2;      var xpos = (screen.width-600)/2;
     xpos = (xpos < 0) ? '0' : xpos;      xpos = (xpos < 0) ? '0' : xpos;
     var ypos = (screen.height-height)/2-30;      var ypos = (screen.height-height)/2-30;
     ypos = (ypos < 0) ? '0' : ypos;      ypos = (ypos < 0) ? '0' : ypos;
   
     pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);      pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars=yes,screenx='+xpos+',screeny='+ypos+',width=700,height='+height);
     pWin.focus();      pWin.focus();
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.$docopen;      pDoc.$docopen;
Line 1480  INNERJS Line 1675  INNERJS
   
     pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");      pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");      pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
     pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />");      pDoc.write("<h1>&nbsp;$html_js_lt{'comp'}\"+fullname+\"<\\/h1>");
   
     pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");      pDoc.write('<table style="border:1px solid black;"><tr>');
     pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");      pDoc.write("<td><b>$html_js_lt{'incl'}<\\/b><\\/td><td><b>$html_js_lt{'type'}<\\/b><\\/td><td><b>$html_js_lt{'mesa'}<\\/td><\\/tr>");
     pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>");  
 }  }
     function displaySubject(msg,shwsel) {      function displaySubject(msg,shwsel) {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc.write("<tr>");
     pDoc.write("<td>Subject<\\/td>");  
     pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");      pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
     pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"><\\/td><\\/tr>");      pDoc.write("<td>$html_js_lt{'subj'}<\\/td>");
       pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"40\\" maxlength=\\"80\\"><\\/td><\\/tr>");
 }  }
   
   function displaySavedMsg(ctr,msg,shwsel) {    function displaySavedMsg(ctr,msg,shwsel) {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc.write("<tr>");
     pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");  
     pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");      pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
       pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
     pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");      pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");
 }  }
   
   function newMsg(newmsg,shwsel) {    function newMsg(newmsg,shwsel) {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc.write("<tr>");
     pDoc.write("<td align=\\"center\\">New<\\/td>");  
     pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");      pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
       pDoc.write("<td align=\\"center\\">$html_js_lt{'new'}<\\/td>");
     pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");      pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");
 }  }
   
   function msgTail() {    function msgTail() {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("<\\/table>");      //pDoc.write("<\\/table>");
     pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");      pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
     pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");      pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
     pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");      pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\"><br /><br />");
     pDoc.write("<\\/form>");      pDoc.write("<\\/form>");
     pDoc.write('$end_page_msg_central');      pDoc.write('$end_page_msg_central');
     pDoc.close();      pDoc.close();
Line 1529  INNERJS Line 1723  INNERJS
     var redsel = "";      var redsel = "";
     var grnsel = "";      var grnsel = "";
     var blusel = "";      var blusel = "";
     if (kwclr=="red")   {var redsel="checked"};      var txtcol1 = "$js_lt{'col1'}";
     if (kwclr=="green") {var grnsel="checked"};      var txtcol2 = "$js_lt{'col2'}";
     if (kwclr=="blue")  {var blusel="checked"};      var txtcol3 = "$js_lt{'col3'}";
       var txtsiz1 = "$js_lt{'siz1'}";
       var txtsiz2 = "$js_lt{'siz2'}";
       var txtsiz3 = "$js_lt{'siz3'}";
       var txtsty1 = "$js_lt{'sty1'}";
       var txtsty2 = "$js_lt{'sty2'}";
       var txtsty3 = "$js_lt{'sty3'}";
       if (kwclr=="red")   {var redsel="checked='checked'"};
       if (kwclr=="green") {var grnsel="checked='checked'"};
       if (kwclr=="blue")  {var blusel="checked='checked'"};
     var sznsel = "";      var sznsel = "";
     var sz1sel = "";      var sz1sel = "";
     var sz2sel = "";      var sz2sel = "";
     if (kwsize=="0")  {var sznsel="checked"};      if (kwsize=="0")  {var sznsel="checked='checked'"};
     if (kwsize=="+1") {var sz1sel="checked"};      if (kwsize=="+1") {var sz1sel="checked='checked'"};
     if (kwsize=="+2") {var sz2sel="checked"};      if (kwsize=="+2") {var sz2sel="checked='checked'"};
     var synsel = "";      var synsel = "";
     var syisel = "";      var syisel = "";
     var sybsel = "";      var sybsel = "";
     if (kwstyle=="")    {var synsel="checked"};      if (kwstyle=="")    {var synsel="checked='checked'"};
     if (kwstyle=="<i>") {var syisel="checked"};      if (kwstyle=="<i>") {var syisel="checked='checked'"};
     if (kwstyle=="<b>") {var sybsel="checked"};      if (kwstyle=="<b>") {var sybsel="checked='checked'"};
     highlightCentral();      highlightCentral();
     highlightbody('red','red',redsel,'0','normal',sznsel,'','normal',synsel);      highlightbody('red',txtcol1,redsel,'0',txtsiz1,sznsel,'',txtsty1,synsel);
     highlightbody('green','green',grnsel,'+1','+1',sz1sel,'<i>','italic',syisel);      highlightbody('green',txtcol2,grnsel,'+1',txtsiz2,sz1sel,'<i>',txtsty2,syisel);
     highlightbody('blue','blue',blusel,'+2','+2',sz2sel,'<b>','bold',sybsel);      highlightbody('blue',txtcol3,blusel,'+2',txtsiz3,sz2sel,'<b>',txtsty3,sybsel);
     highlightend();      highlightend();
     return;      return;
   }    }
Line 1565  INNERJS Line 1768  INNERJS
     hDoc.$docopen;      hDoc.$docopen;
     hDoc.write('$start_page_highlight_central');      hDoc.write('$start_page_highlight_central');
     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");      hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
     hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options<\\/span><\\/h3><br /><br />");      hDoc.write("<h1>$html_js_lt{'kehi'}<\\/h1>");
   
     hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");      hDoc.write('<table border="0" width="100%"><tr style="background-color:#A1D676">');
     hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");      hDoc.write("<th>$html_js_lt{'txtc'}<\\/th><th>$html_js_lt{'font'}<\\/th><th>$html_js_lt{'fnst'}<\\/th><\\/tr>");
     hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>");  
   }    }
   
   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) {     function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
     var hDoc = hwdWin.document;      var hDoc = hwdWin.document;
     hDoc.write("<tr bgcolor=\\"#ffffdd\\">");      hDoc.write("<tr>");
     hDoc.write("<td align=\\"left\\">");      hDoc.write("<td align=\\"left\\">");
     hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"<\\/td>");      hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+" \\/>&nbsp;"+clrtxt+"<\\/td>");
     hDoc.write("<td align=\\"left\\">");      hDoc.write("<td align=\\"left\\">");
     hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"<\\/td>");      hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+" \\/>&nbsp;"+sztxt+"<\\/td>");
     hDoc.write("<td align=\\"left\\">");      hDoc.write("<td align=\\"left\\">");
     hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"<\\/td>");      hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+" \\/>&nbsp;"+sytxt+"<\\/td>");
     hDoc.write("<\\/tr>");      hDoc.write("<\\/tr>");
   }    }
   
   function highlightend() {     function highlightend() { 
     var hDoc = hwdWin.document;      var hDoc = hwdWin.document;
     hDoc.write("<\\/table>");      hDoc.write("<\\/table><br \\/>");
     hDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");      hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:updateChoice(1)\\" \\/>&nbsp;&nbsp;");
     hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");      hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\" \\/><br /><br />");
     hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");  
     hDoc.write("<\\/form>");      hDoc.write("<\\/form>");
     hDoc.write('$end_page_highlight_central');      hDoc.write('$end_page_highlight_central');
     hDoc.close();      hDoc.close();
   }    }
   
 </script>  
 SUBJAVASCRIPT  SUBJAVASCRIPT
 }  }
   
Line 1608  sub get_increment { Line 1808  sub get_increment {
     return $increment;      return $increment;
 }  }
   
   sub gradeBox_start {
       return (
           &Apache::loncommon::start_data_table()
          .&Apache::loncommon::start_data_table_header_row()
          .'<th>'.&mt('Part').'</th>'
          .'<th>'.&mt('Points').'</th>'
          .'<th>&nbsp;</th>'
          .'<th>'.&mt('Assign Grade').'</th>'
          .'<th>'.&mt('Weight').'</th>'
          .'<th>'.&mt('Grade Status').'</th>'
          .&Apache::loncommon::end_data_table_header_row()
       );
   }
   
   sub gradeBox_end {
       return (
           &Apache::loncommon::end_data_table()
       );
   }
 #--- displays the grading box, used in essay type problem and grading by page/sequence  #--- displays the grading box, used in essay type problem and grading by page/sequence
 sub gradeBox {  sub gradeBox {
     my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;      my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
     my $checkIcon = '<img alt="'.&mt('Check Mark').      my $checkIcon = '<img alt="'.&mt('Check Mark').
  '" src="'.$request->dir_config('lonIconsURL').   '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
  '/check.gif" height="16" border="0" />';  
     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);      my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
     my $wgtmsg = ($wgt > 0 ? '(problem weight)' :       my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)') 
   '<span class="LC_info">problem weight assigned by computer</span>');                             : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
     $wgt       = ($wgt > 0 ? $wgt : '1');      $wgt       = ($wgt > 0 ? $wgt : '1');
     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?      my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
   '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));    '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
     my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";      my $data_WGT='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
     my $display_part=&get_display_part($partid,$symb);      my $display_part= &get_display_part($partid,$symb);
     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},      my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
        [$partid]);         [$partid]);
     my $aggtries = $$record{'resource.'.$partid.'.tries'};      my $aggtries = $$record{'resource.'.$partid.'.tries'};
     if ($last_resets{$partid}) {      if ($last_resets{$partid}) {
         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);          $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
     }      }
     $result.='<table border="0"><tr><td>'.      my $result=&Apache::loncommon::start_data_table_row();
  '<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";  
     my $ctr = 0;      my $ctr = 0;
     my $thisweight = 0;      my $thisweight = 0;
     my $increment = &get_increment();      my $increment = &get_increment();
     $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across  
       my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
     while ($thisweight<=$wgt) {      while ($thisweight<=$wgt) {
  $result.= '<td><span style="white-space: nowrap;"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.   $radio.= '<td><span class="LC_nobreak"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
     'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.          'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
     $thisweight.')" value="'.$thisweight.'" '.      $thisweight.')" value="'.$thisweight.'" '.
     ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";      ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
  $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');   $radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
         $thisweight += $increment;          $thisweight += $increment;
  $ctr++;   $ctr++;
     }      }
     $result.='</tr></table>';      $radio.='</tr></table>';
     $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>'."\n";  
     $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.      my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
  ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.   ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
  'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.   'onchange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
  $wgt.')" /></td>'."\n";   $wgt.')" /></td>'."\n";
     $result.='<td>/'.$wgt.' '.$wgtmsg.      $line.='<td>/'.$wgt.' '.$wgtmsg.
  ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').   ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
  ' </td><td>'."\n";   ' </td>'."\n";
     $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.      $line.='<td><select name="GD_SEL'.$counter.'_'.$partid.'" '.
  'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";   'onchange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {      if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
  $result.='<option></option>'.   $line.='<option></option>'.
     '<option selected="selected">excused</option>';      '<option value="excused" selected="selected">'.&mt('excused').'</option>';
     } else {      } else {
  $result.='<option selected="selected"></option>'.   $line.='<option selected="selected"></option>'.
     '<option>excused</option>';      '<option value="excused" >'.&mt('excused').'</option>';
     }      }
     $result.='<option>reset status</option></select>'."\n";      $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
     $result.="&nbsp;&nbsp;\n";  
   
       $result .= 
       '<td>'.$data_WGT.$display_part.'</td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>';
       $result.=&Apache::loncommon::end_data_table_row();
       $result.=&Apache::loncommon::start_data_table_row().'<td colspan="6">';
     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".      $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
  '<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".   '<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
  '<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.   '<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
Line 1671  sub gradeBox { Line 1894  sub gradeBox {
         $$record{'resource.'.$partid.'.tries'}.'" />'."\n".          $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.          '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
         $aggtries.'" />'."\n";          $aggtries.'" />'."\n";
     $result.='</td></tr></table>'."\n";      my $res_error;
     $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);      $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record,\$res_error);
       $result.='</td>'.&Apache::loncommon::end_data_table_row();
       if ($res_error) {
           return &navmap_errormsg();
       }
     return $result;      return $result;
 }  }
   
 sub handback_box {  sub handback_box {
     my ($symb,$uname,$udom,$counter,$partid,$record) = @_;      my ($symb,$uname,$udom,$counter,$partid,$record,$res_error_pointer) = @_;
     my ($partlist,$handgrade,$responseType) = &response_type($symb);      my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error_pointer);
     my (@respids);      my (@respids);
      my @part_response_id = &flatten_responseType($responseType);      my @part_response_id = &flatten_responseType($responseType);
     foreach my $part_response_id (@part_response_id) {      foreach my $part_response_id (@part_response_id) {
     my ($part,$resp) = @{ $part_response_id };      my ($part,$resp) = @{ $part_response_id };
         if ($part eq $partid) {          if ($part eq $partid) {
Line 1692  sub handback_box { Line 1919  sub handback_box {
  my $prefix = $counter.'_'.$partid.'_'.$respid.'_';   my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
  my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);   my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
  next if (!@$files);   next if (!@$files);
  my $file_counter = 1;   my $file_counter = 0;
  foreach my $file (@$files) {   foreach my $file (@$files) {
     if ($file =~ /\/portfolio\//) {      if ($file =~ /\/portfolio\//) {
                   $file_counter++;
            my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);             my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
            my ($name,$version,$ext) = &file_name_version_ext($file_disp);             my ($name,$version,$ext) = &Apache::lonnet::file_name_version_ext($file_disp);
            $file_disp = "$name.$ext";             $file_disp = "$name.$ext";
            $file = $file_path.$file_disp;             $file = $file_path.$file_disp;
            $result.=&mt('Return commented version of [_1] to student.',             $result.=&mt('Return commented version of [_1] to student.',
     '<span class="LC_filename">'.$file_disp.'</span>');      '<span class="LC_filename">'.$file_disp.'</span>');
            $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";             $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
            $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />';             $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />'."\n";
            $result.='(File will be uploaded when you click on Save & Next below.)<br />';  
            $file_counter++;  
     }      }
  }   }
           if ($file_counter) {
               $result .= '<input type="hidden" name="'.$prefix.'countreturndoc" value="'.$file_counter.'" />'."\n".
                          '<span class="LC_info">'.
                          '('.&mt('File(s) will be uploaded when you click on Save &amp; Next below.',$file_counter).')</span><br /><br />';
           }
     }      }
     return $result;          return $result;    
 }  }
Line 1739  sub show_problem { Line 1970  sub show_problem {
  $companswer=~s|</form>||g;   $companswer=~s|</form>||g;
  $companswer=~s|name="submit"|name="would_have_been_submit"|g;   $companswer=~s|name="submit"|name="would_have_been_submit"|g;
     }      }
     my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';      my $renderheading = &mt('View of the problem');
     $result.='<table border="0" width="100%">';      my $answerheading = &mt('Correct answer');
     if ($viewon) {      if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
  $result.='<tr><td bgcolor="#e6ffff"><b> ';          my $stu_fullname = $env{'form.fullname'};
  if ($mode eq 'both' or $mode eq 'text') {          if ($stu_fullname eq '') {
     $result.='View of the problem - ';              $stu_fullname = &Apache::loncommon::plainname($uname,$udom,'lastname');
  } else {          }
     $result.='Correct answer: ';          my $forwhom = &nameUserString(undef,$stu_fullname,$uname,$udom);
  }          if ($forwhom ne '') {
  $result.=$env{'form.fullname'}.'</b></td></tr>';              $renderheading = &mt('View of the problem for[_1]',$forwhom);
               $answerheading = &mt('Correct answer for[_1]',$forwhom);
           }
     }      }
       $rendered=
           '<div class="LC_Box">'
          .'<h3 class="LC_hcell">'.$renderheading.'</h3>'
          .$rendered
          .'</div>';
       $companswer=
           '<div class="LC_Box">'
          .'<h3 class="LC_hcell">'.$answerheading.'</h3>'
          .$companswer
          .'</div>';
       my $result;
     if ($mode eq 'both') {      if ($mode eq 'both') {
  $result.='<tr><td bgcolor="#ffffff">'.$rendered.'<br />';          $result=$rendered.$companswer;
  $result.='<b>Correct answer:</b><br />'.$companswer;  
     } elsif ($mode eq 'text') {      } elsif ($mode eq 'text') {
  $result.='<tr><td bgcolor="#ffffff">'.$rendered;          $result=$rendered;
     } elsif ($mode eq 'answer') {      } elsif ($mode eq 'answer') {
  $result.='<tr><td bgcolor="#ffffff">'.$companswer;          $result=$companswer;
     }      }
     $result.='</td></tr></table>';  
     $result.='</td></tr></table><br />';  
     return $result;      return $result;
 }  }
   
 sub files_exist {  sub files_exist {
     my ($r, $symb) = @_;      my ($r, $symb) = @_;
     my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');      my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
   
     foreach my $student (@students) {      foreach my $student (@students) {
         my ($uname,$udom,$fullname) = split(/:/,$student);          my ($uname,$udom,$fullname) = split(/:/,$student);
         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},          my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
Line 1785  sub files_exist { Line 2025  sub files_exist {
   
 sub download_all_link {  sub download_all_link {
     my ($r,$symb) = @_;      my ($r,$symb) = @_;
       unless (&files_exist($r, $symb)) {
           $r->print(&mt('There are currently no submitted documents.'));
           return;
       }
     my $all_students =       my $all_students = 
  join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));   join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
   
Line 1792  sub download_all_link { Line 2036  sub download_all_link {
  join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));   join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
   
     my $identifier = &Apache::loncommon::get_cgi_id();      my $identifier = &Apache::loncommon::get_cgi_id();
     &Apache::lonnet::appenv('cgi.'.$identifier.'.students' => $all_students,      &Apache::lonnet::appenv({'cgi.'.$identifier.'.students' => $all_students,
                             'cgi.'.$identifier.'.symb' => $symb,                               'cgi.'.$identifier.'.symb' => $symb,
                             'cgi.'.$identifier.'.parts' => $parts,);                               'cgi.'.$identifier.'.parts' => $parts,});
     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.      $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
       &mt('Download All Submitted Documents').'</a>');        &mt('Download All Submitted Documents').'</a>');
     return      return;
   }
   
   sub submit_download_link {
       my ($request,$symb) = @_;
       if (!$symb) { return ''; }
   #FIXME: Figure out which type of problem this is and provide appropriate download
       my $res_error;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error);
       if (ref($res_error)) {
           if ($$res_error) {
               $request->print(&mt('An error occurred retrieving response types'));
               return;
           }
       }
       my ($numupload,$numessay) = (0,0);
       if (ref($responseType) eq 'HASH') {
           foreach my $part (sort(keys(%$responseType))) {
               foreach my $id (sort(keys(%{ $responseType->{$part} }))) {
                   my $responsetype = $responseType->{$part}->{$id};
                   if ($responsetype eq 'essay') {
                       my $uploadedfiletypes =
                           &Apache::lonnet::EXT("resource.$part".'_'."$id.uploadedfiletypes",$symb);
                       if ($uploadedfiletypes) {
                           $numupload++;
                       } else {
                           $numessay++;
                       }
                   }
               }
           }
       }
       if (($numupload) || ($numessay)) {
           my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
           my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
           my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
           (undef,undef,my $fullname) = &getclasslist($getsec,1,$getgroup,$symb,$submitonly,1);
           if (ref($fullname) eq 'HASH') {
               my @students = map { $_.':'.$fullname->{$_} } (keys(%{$fullname}));
               if (@students) {
                   @{$env{'form.stuinfo'}} = @students;
                   if ($numupload) {
                       &download_all_link($request,$symb);
                   }
   # FIXME Need to provide a mechanism to download essays, i.e., if $numessay > 0
   # Needs to omit user's identity if resource instance is for an anonymous survey.
               } else {
                   $request->print(&mt('No students match the criteria you selected'));
               }
           } else {
               $request->print(&mt('Could not retrieve student information'));
           }
       } else {
           $request->print(&mt('No essayresponse items found'));
       }
       return;
 }  }
   
 sub build_section_inputs {  sub build_section_inputs {
Line 1815  sub build_section_inputs { Line 2114  sub build_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,$symb) = @_;
     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 $probtitle=&Apache::lonnet::gettitle($symb); 
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }      if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
       my $is_tool = ($symb =~ /ext\.tool$/);
       my ($essayurl,%coursedesc_by_cid);
   
     if (!&canview($usec)) {      if (!&canview($usec)) {
  $request->print('<span class="LC_warning">Unable to view requested student.('.          $request->print(
  $uname.':'.$udom.' in section '.$usec.' in course id '.              '<span class="LC_warning">'.
  $env{'request.course.id'}.')</span>');              &mt('Unable to view requested student.').
  $request->print(&show_grading_menu_form($symb));              ' '.&mt('([_1] in section [_2] in course id [_3])',
                           $uname.':'.$udom,$usec,$env{'request.course.id'}).
               '</span>');
  return;   return;
     }      }
   
     if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }      if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
     if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }      unless ($is_tool) { 
     if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }          if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
           if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
       }
     my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');      my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
     my $checkIcon = '<img alt="'.&mt('Check Mark').      my $checkIcon = '<img alt="'.&mt('Check Mark').
  '" 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);
  &sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes');   &sub_page_kw_js($request);
  $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ?   
     &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};  
  if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {  
     &download_all_link($request, $symb);  
  }  
  $request->print('<h3>&nbsp;<span class="LC_info">Submission Record</span></h3>'."\n".  
  '<h4>&nbsp;<b>Resource: </b>'.$env{'form.probTitle'}.'</h4>'."\n");  
   
  if ($env{'form.handgrade'} eq 'no') {  
     my $checkMark='<br /><br />&nbsp;<b>Note:</b> Part(s) graded correct by the computer is marked with a '.  
  $checkIcon.' symbol.'."\n";  
     $request->print($checkMark);  
  }  
   
  # option to display problem, only once else it cause problems    # option to display problem, only once else it cause problems 
         # with the form later since the problem has a form.          # with the form later since the problem has a form.
Line 1873  sub submission { Line 2165  sub submission {
     $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 not to be blank 
         # if this subroutine has been called once.          # if this subroutine has been called once.
  my %keyhash = ();   my %keyhash = ();
  if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {  # if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
           if (1) {
     %keyhash = &Apache::lonnet::dump('nohist_handgrade',      %keyhash = &Apache::lonnet::dump('nohist_handgrade',
      $env{'course.'.$env{'request.course.id'}.'.domain'},       $env{'course.'.$env{'request.course.id'}.'.domain'},
      $env{'course.'.$env{'request.course.id'}.'.num'});       $env{'course.'.$env{'request.course.id'}.'.num'});
Line 1887  sub submission { Line 2180  sub submission {
     $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';      $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
     $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';      $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
     $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ?       $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
  $keyhash{$symb.'_subject'} : $env{'form.probTitle'};   $keyhash{$symb.'_subject'} : $probtitle;
     $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'));   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="Status"     value="'.$stu_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="refresh"    value="off" />'."\n".   '<input type="hidden" name="refresh"    value="off" />'."\n".
  '<input type="hidden" name="studentNo"  value="" />'."\n".   '<input type="hidden" name="studentNo"  value="" />'."\n".
  '<input type="hidden" name="gradeOpt"   value="" />'."\n".   '<input type="hidden" name="gradeOpt"   value="" />'."\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="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".  
  '<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".
  &build_section_inputs().   &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="NCT"'.   '<input type="hidden" name="NCT"'.
  ' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");   ' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
  if ($env{'form.handgrade'} eq 'yes') {  # if ($env{'form.handgrade'} eq 'yes') {
           if (1) {
     $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".      $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
     '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".      '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
     '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".      '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
Line 1936  sub submission { Line 2226  sub submission {
  }   }
  $request->print($prnmsg);   $request->print($prnmsg);
   
  if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {  # if ($env{'form.handgrade'} eq 'yes') {
           unless ($is_tool) {
   
               my %lt = &Apache::lonlocal::texthash(
                             keyh => 'Keyword Highlighting for Essays',
                             keyw => 'Keyword Options',
                             list => 'List',
                             past => 'Paste Selection to List',
                             high => 'Highlight Attribute',
                        );    
 #  #
 # Print out the keyword options line  # Print out the keyword options line
 #  #
     $request->print(<<KEYWORDS);      $request->print(
 &nbsp;<b>Keyword Options:</b>&nbsp;                  '<div class="LC_columnSection">'
 <a href="javascript:keywords(document.SCORE);" target="_self">List</a>&nbsp; &nbsp;                 .'<fieldset><legend>'.$lt{'keyh'}.'</legend>'
 <a href="#" onMouseDown="javascript:getSel(); return false"                 .&Apache::lonhtmlcommon::funclist_from_array(
  CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;                      ['<a href="javascript:keywords(document.SCORE);" target="_self">'.$lt{'list'}.'</a>',
 <a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br />                       '<a href="#" onmousedown="javascript:getSel(); return false"
 KEYWORDS   class="page">'.$lt{'past'}.'</a>',
                        '<a href="javascript:kwhighlight();" target="_self">'.$lt{'high'}.'</a>'],
                       {legend => $lt{'keyw'}})
                  .'</fieldset></div>'
               );
   
 #  #
 # Load the other essays for similarity check  # Load the other essays for similarity check
 #  #
             my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);              (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
     my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);              if ($essayurl eq 'lib/templates/simpleproblem.problem') {
     $apath=&escape($apath);                  my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
     $apath=~s/\W/\_/gs;                  my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
     %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);                  if ($cdom ne '' && $cnum ne '') {
                       my ($map,$id,$res) = &Apache::lonnet::decode_symb($symb);
                       if ($map =~ m{^\Quploaded/$cdom/$cnum/\E(default(?:|_\d+)\.(?:sequence|page))$}) {
                           my $apath = $1.'_'.$id;
                           $apath=~s/\W/\_/gs;
                           &init_old_essays($symb,$apath,$cdom,$cnum);
                       }
                   }
               } else {
           my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
           $apath=&escape($apath);
           $apath=~s/\W/\_/gs;
                   &init_old_essays($symb,$apath,$adom,$aname);
               }
         }          }
     }      }
   
 # This is where output for one specific student would start  # This is where output for one specific student would start
     my $bgcolor='#DDEEDD';      my $add_class = ($counter%2) ? ' LC_grade_show_user_odd_row' : '';
     if ($counter%2) { $bgcolor='#DDDDEE'; }      $request->print(
     $request->print("\n\n".          "\n\n"
                     '<p><table border="2"><tr><th bgcolor="'.$bgcolor.'">'.$env{'form.fullname'}.'</th></tr><tr><td bgcolor="'.$bgcolor.'">');         .'<div class="LC_grade_show_user'.$add_class.'">'
          .'<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</h2>'
          ."\n"
       );
   
       # Show additional functions if allowed
       if ($perm{'vgr'}) {
           $request->print(
               &Apache::loncommon::track_student_link(
                   'View recent activity',
                   $uname,$udom,'check')
              .' '
           );
       }
       if ($perm{'opa'}) {
           $request->print(
               &Apache::loncommon::pprmlink(
                   &mt('Set/Change parameters'),
                   $uname,$udom,$symb,'check'));
       }
   
       # Show Problem
     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {      if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
  my $mode;   my $mode;
  if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {   if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
Line 1974  KEYWORDS Line 2311  KEYWORDS
     $mode='answer';      $mode='answer';
  }   }
  &Apache::lonxml::clear_problem_counter();   &Apache::lonxml::clear_problem_counter();
  $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode));   $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,{'request.prefix' => 'ctr'.$counter}));
     }      }
   
     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);      my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
     my ($partlist,$handgrade,$responseType) = &response_type($symb);      my $res_error;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
       if ($res_error) {
           $request->print(&navmap_errormsg());
           return;
       }
   
     # Display student info      # Display student info
     $request->print(($counter == 0 ? '' : '<br />'));      $request->print(($counter == 0 ? '' : '<br />'));
     my $result='<table border="0" width="100%"><tr><td bgcolor="#777777">'."\n".  
  '<table border="0" width="100%"><tr bgcolor="#edffff"><td>'."\n";  
   
     $result.='<b>Fullname: </b>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'<br />'."\n";      my $boxtitle = &mt('Submissions');
       if ($is_tool) {
           $boxtitle = &mt('Transactions')
       }
       my $result='<div class="LC_Box">'
                 .'<h3 class="LC_hcell">'.$boxtitle.'</h3>';
     $result.='<input type="hidden" name="name'.$counter.      $result.='<input type="hidden" name="name'.$counter.
  '" value="'.$env{'form.fullname'}.'" />'."\n";               '" value="'.$env{'form.fullname'}.'" />'."\n";
   #    if ($env{'form.handgrade'} eq 'no') {
       unless ($is_tool) {
           $result.='<p class="LC_info">'
                   .&mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)
                   ."</p>\n";
       }
   
     # If any part of the problem is an essay-response (handgraded), then check for collaborators      # If any part of the problem is an essay-response (handgraded), then check for collaborators
     my $fullname;      my $fullname;
     my $col_fullnames = [];      my $col_fullnames = [];
     if ($env{'form.handgrade'} eq 'yes') {  #    if ($env{'form.handgrade'} eq 'yes') {
       unless ($is_tool) {
  (my $sub_result,$fullname,$col_fullnames)=   (my $sub_result,$fullname,$col_fullnames)=
     &check_collaborators($symb,$uname,$udom,\%record,$handgrade,      &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
  $counter);   $counter);
  $result.=$sub_result;   $result.=$sub_result;
     }      }
     $request->print($result."\n");      $request->print($result."\n");
       
     # print student answer/submission      # print student answer/submission
     # Options are (1) Handgaded submission only      # Options are (1) Handgraded submission only
     #             (2) Last submission, includes submission that is not handgraded       #             (2) Last submission, includes submission that is not handgraded 
     #                  (for multi-response type part)      #                  (for multi-response type part)
     #             (3) Last submission plus the parts info      #             (3) Last submission plus the parts info
     #             (4) The whole record for this student      #             (4) The whole record for this student
     if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {      
  my ($string,$timestamp)= &get_last_submission(\%record);      my ($string,$timestamp)= &get_last_submission(\%record,$is_tool);
  my $lastsubonly=''.  
     ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '.      my $lastsubonly;
      $$timestamp)."</td></tr>\n";  
  if ($$timestamp eq '') {      if ($$timestamp eq '') {
     $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0];           $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
  } else {      } elsif ($is_tool) {
     my %seenparts;          $lastsubonly =
     my @part_response_id = &flatten_responseType($responseType);              '<div class="LC_grade_submissions_body">'
     foreach my $part (@part_response_id) {             .'<b>'.&mt('Date Grade Passed Back:').'</b> '.$$timestamp."</div>\n";
  next if ($env{'form.lastSub'} eq 'hdgrade'       } else {
           $lastsubonly =
               '<div class="LC_grade_submissions_body">'
              .'<b>'.&mt('Date Submitted:').'</b> '.$$timestamp."\n";
   
    my %seenparts;
    my @part_response_id = &flatten_responseType($responseType);
    foreach my $part (@part_response_id) {
       next if ($env{'form.lastSub'} eq 'hdgrade' 
  && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');   && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
   
  my ($partid,$respid) = @{ $part };      my ($partid,$respid) = @{ $part };
  my $display_part=&get_display_part($partid,$symb);      my $display_part=&get_display_part($partid,$symb);
  if ($env{"form.$uname:$udom:$partid:submitted_by"}) {      if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
     if (exists($seenparts{$partid})) { next; }   if (exists($seenparts{$partid})) { next; }
     $seenparts{$partid}=1;   $seenparts{$partid}=1;
     my $submitby='<b>Part:</b> '.$display_part.                  $request->print(
  ' <b>Collaborative submission by:</b> '.                      '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
  '<a href="javascript:viewSubmitter(\''.                      ' <b>'.&mt('Collaborative submission by: [_1]',
  $env{"form.$uname:$udom:$partid:submitted_by"}.                                 '<a href="javascript:viewSubmitter(\''.
  '\');" target="_self">'.                                 $env{"form.$uname:$udom:$partid:submitted_by"}.
  $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';                                 '\');" target="_self">'.
     $request->print($submitby);                                 $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a>').
     next;                      '<br />');
  }   next;
  my $responsetype = $responseType->{$partid}->{$respid};   }
  if (!exists($record{"resource.$partid.$respid.submission"})) {      my $responsetype = $responseType->{$partid}->{$respid};
     $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.      if (!exists($record{"resource.$partid.$respid.submission"})) {
  $display_part.' <span class="LC_internal_info">( ID '.$respid.                  $lastsubonly.="\n".'<div class="LC_grade_submission_part">'.
  ' )</span>&nbsp; &nbsp;'.                      '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
  '<span class="LC_warning">Nothing submitted - no attempts</span><br /><br />';                      ' <span class="LC_internal_info">'.
     next;                      '('.&mt('Response ID: [_1]',$respid).')'.
  }                      '</span>&nbsp; &nbsp;'.
  foreach (@$string) {            '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
     my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/;   next;
     if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }      }
     my ($ressub,$subval) = split(/:/,$_,2);      foreach my $submission (@$string) {
     # Similarity check   my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
     my $similar='';   if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
     if($env{'form.checkPlag'}){   my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
  my ($oname,$odom,$ocrsid,$oessay,$osim)=   # Similarity check
     &most_similar($uname,$udom,$subval,\%old_essays);                  my $similar='';
  if ($osim) {                  my ($type,$trial,$rndseed);
     $osim=int($osim*100.0);                  if ($hide eq 'rand') {
     my %old_course_desc =                       $type = 'randomizetry';
  &Apache::lonnet::coursedescription($ocrsid,                      $trial = $record{"resource.$partid.tries"};
    {'one_time' => 1});                      $rndseed = $record{"resource.$partid.rndseed"};
                   }
     $similar="<hr /><h3><span class=\"LC_warning\">".          if ($env{'form.checkPlag'}) {
  &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])',         my ($oname,$odom,$ocrsid,$oessay,$osim)=
     $osim,          &most_similar($uname,$udom,$symb,$subval);
     &Apache::loncommon::plainname($oname,$odom),      if ($osim) {
     $oname,$odom,   $osim=int($osim*100.0);
     $old_course_desc{'description'},                          if ($hide eq 'anon') {
     $old_course_desc{'num'},                              $similar='<hr /><span class="LC_warning">'.&mt("Essay was found to be similar to another essay submitted for this assignment.").'<br />'.
     $old_course_desc{'domain'}).                                       &mt('As the current submission is for an anonymous survey, no other details are available.').'</span><hr />';
  '</span></h3><blockquote><i>'.                          } else {
  &keywords_highlight($oessay).      $similar='<hr />';
  '</i></blockquote><hr />';                              if ($essayurl eq 'lib/templates/simpleproblem.problem') {
  }                                  $similar .= '<h3><span class="LC_warning">'.
     }                                              &mt('Essay is [_1]% similar to an essay by [_2]',
     my $order=&get_order($partid,$respid,$symb,$uname,$udom);                                                  $osim,
     if ($env{'form.lastSub'} eq 'lastonly' ||                                                   &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')').
  ($env{'form.lastSub'} eq 'hdgrade' &&                                               '</span></h3>';
                               } else {
                                   my %old_course_desc;
                                   if ($ocrsid ne '') {
                                       if (ref($coursedesc_by_cid{$ocrsid}) eq 'HASH') {
                                           %old_course_desc = %{$coursedesc_by_cid{$ocrsid}};
                                       } else {
                                           my $args;
                                           if ($ocrsid ne $env{'request.course.id'}) {
                                               $args = {'one_time' => 1};
                                           }
                                           %old_course_desc =
                                               &Apache::lonnet::coursedescription($ocrsid,$args);
                                           $coursedesc_by_cid{$ocrsid} = \%old_course_desc;
                                       }
                                       $similar .=
                                           '<h3><span class="LC_warning">'.
                                           &mt('Essay is [_1]% similar to an essay by [_2] in course [_3] (course id [_4]:[_5])',
                                               $osim,
                                               &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')',
                                               $old_course_desc{'description'},
                                               $old_course_desc{'num'},
                                               $old_course_desc{'domain'}).
                                           '</span></h3>';
                                   } else {
                                       $similar .=
                                           '<h3><span class="LC_warning">'.
                                           &mt('Essay is [_1]% similar to an essay by [_2] in an unknown course',
                                               $osim,
                                               &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')').
                                           '</span></h3>';
                                   }
                               }
                               $similar .= '<blockquote><i>'.
                                           &keywords_highlight($oessay).
                                           '</i></blockquote><hr />';
                           }
               }
    }
    my $order=&get_order($partid,$respid,$symb,$uname,$udom,
                                        undef,$type,$trial,$rndseed);
                   if ($env{'form.lastSub'} eq 'lastonly' || $env{'form.lastSub'} eq 'datesub' || $env{'form.lastSub'} =~ /^(last|all)$/ || ($env{'form.lastSub'} eq 'hdgrade' && 
  $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {   $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
  my $display_part=&get_display_part($partid,$symb);      my $display_part=&get_display_part($partid,$symb);
  $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.                      $lastsubonly.='<div class="LC_grade_submission_part">'.
     $display_part.' <span class="LC_internal_info">( ID '.$respid.                          '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
     ' )</span>&nbsp; &nbsp;';                          ' <span class="LC_internal_info">'.
  my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);                          '('.&mt('Response ID: [_1]',$respid).')'.
  if (@$files) {                          '</span>&nbsp; &nbsp;';
     $lastsubonly.='<br /><span class="LC_warning">Like all files provided by users, this file may contain virusses</span><br />';      my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
     my $file_counter = 0;                          
     foreach my $file (@$files) {      if (@$files) {
         $file_counter ++;                          if ($hide eq 'anon') {
  &Apache::lonnet::allowuploaded('/adm/grades',$file);                              $lastsubonly.='<br />'.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
  $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';                          } else {
     }                              $lastsubonly.='<br /><br />'.'<b>'.&mt('Submitted Files:').'</b>'
     $lastsubonly.='<br />';                                          .'<br /><span class="LC_warning">';
  }                              if(@$files == 1) {
  $lastsubonly.='<b>Submitted Answer: </b>'.                                  $lastsubonly .= &mt('Like all files provided by users, this file may contain viruses!');
                               } else {
                                   $lastsubonly .= &mt('Like all files provided by users, these files may contain viruses!');
                               }
                               $lastsubonly .= '</span>';                         
                               foreach my $file (@$files) {
                                   &Apache::lonnet::allowuploaded('/adm/grades',$file);
                                   $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" alt="" /> '.$file.'</a>';
                               }
                           }
    $lastsubonly.='<br />';
                       }
                       if ($hide eq 'anon') {
                           $lastsubonly.='<br /><b>'.&mt('Anonymous Survey').'</b>'; 
                       } else {
                        $lastsubonly.='<br /><b>'.&mt('Submitted Answer:').' </b>';
                           if ($draft) {
                               $lastsubonly.= ' <span class="LC_warning">'.&mt('Draft Copy').'</span>';
                           }
                           $subval =
     &cleanRecord($subval,$responsetype,$symb,$partid,      &cleanRecord($subval,$responsetype,$symb,$partid,
  $respid,\%record,$order);   $respid,\%record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
  if ($similar) {$lastsubonly.="<br /><br />$similar\n";}                          if ($responsetype eq 'essay') {
     }                              $subval =~ s{\n}{<br />}g;
                           }
                           $lastsubonly.=$subval."\n";
                       }
               if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
       $lastsubonly.='</div>';
  }   }
     }              }
  }   }
  $lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\n";   $lastsubonly.='</div>'."\n"; # End: LC_grade_submissions_body
  $request->print($lastsubonly);      }
     } elsif ($env{'form.lastSub'} eq 'datesub') {      $request->print($lastsubonly);
  my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);      if ($env{'form.lastSub'} eq 'datesub') {
           my ($parts,$handgrade,$responseType) = &response_type($symb,\$res_error);
  $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));   $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
     } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {    
  $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,      } 
       if ($env{'form.lastSub'} =~ /^(last|all)$/) {
           my $identifier = (&canmodify($usec)? $counter : '');
           $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
  $env{'request.course.id'},   $env{'request.course.id'},
  $last,'.submission',   $last,'.submission',
  'Apache::grades::keywords_highlight'));   'Apache::grades::keywords_highlight',
                                                                    $usec,$identifier));
     }      }
   
     $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'      $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
  .$udom.'" />'."\n");   .$udom.'" />'."\n");
       
     # return if view submission with no grading option      # return if view submission with no grading option
     if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {      if (!&canmodify($usec)) {
  my $toGrade.='<input type="button" value="Grade Student" '.   $request->print('<p><span class="LC_warning">'.&mt('No grading privileges').'</span></p></div>');
     'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''  
     .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));  
  $toGrade.='</td></tr></table></td></tr></table>'."\n";  
  if (($env{'form.command'} eq 'submission') ||   
     ($env{'form.command'} eq 'processGroup' && $counter == $total)) {  
     $toGrade.='</form>'.&show_grading_menu_form($symb);   
  }  
  $request->print($toGrade);  
  return;   return;
     } else {      } else {
  $request->print('</td></tr></table></td></tr></table>'."\n");   $request->print('</div>'."\n");
     }      }
   
     # essay grading message center      # essay grading message center
     if ($env{'form.handgrade'} eq 'yes') {  #    if ($env{'form.handgrade'} eq 'yes') {
       if (1) {
    my $result='<div class="LC_grade_message_center">';
       
    $result.='<div class="LC_grade_message_center_header">'.
       &mt('Send Message').'</div><div class="LC_grade_message_center_body">';
  my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});   my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
  my $msgfor = $givenn.' '.$lastname;   my $msgfor = $givenn.' '.$lastname;
  if (scalar(@$col_fullnames) > 0) {   if (scalar(@$col_fullnames) > 0) {
Line 2137  KEYWORDS Line 2562  KEYWORDS
     $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';      $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
  }   }
  $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript   $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
  $result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".   $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
     '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";      '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
  $result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.   $result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
     ',\''.$msgfor.'\');" target="_self">'.      ',\''.$msgfor.'\');" target="_self">'.
     &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').'</a><label> ('.      &mt('Compose message to student'.(scalar(@$col_fullnames) >= 1 ? 's' : '')).'</a><label> ('.
     &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.      &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
     '<img src="'.$request->dir_config('lonIconsURL').      ' <img src="'.$request->dir_config('lonIconsURL').
     '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".      '/mailbkgrd.gif" width="14" height="10" alt="" name="mailicon'.$counter.'" />'."\n".
     '<br />&nbsp;('.      '<br />&nbsp;('.
     &mt('Message will be sent when you click on Save & Next below.').")\n";      &mt('Message will be sent when you click on Save &amp; Next below.').")\n";
    $result.='</div></div>';
  $request->print($result);   $request->print($result);
     }      }
     if ($perm{'vgr'}) {  
  $request->print('<br />'.  
     &Apache::loncommon::track_student_link(&mt('View recent activity'),  
    $uname,$udom,'check'));  
     }  
     if ($perm{'opa'}) {  
  $request->print('<br />'.  
     &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),  
  $uname,$udom,$symb,'check'));  
     }  
   
     my %seen = ();      my %seen = ();
     my @partlist;      my @partlist;
     my @gradePartRespid;      my @gradePartRespid;
     my @part_response_id = &flatten_responseType($responseType);      my @part_response_id;
       if ($is_tool) {
           @part_response_id = ([0,'']);
       } else {
           @part_response_id = &flatten_responseType($responseType);
       }
       $request->print(
           '<div class="LC_Box">'
          .'<h3 class="LC_hcell">'.&mt('Assign Grades').'</h3>'
       );
       $request->print(&gradeBox_start());
     foreach my $part_response_id (@part_response_id) {      foreach my $part_response_id (@part_response_id) {
     my ($partid,$respid) = @{ $part_response_id };      my ($partid,$respid) = @{ $part_response_id };
  my $part_resp = join('_',@{ $part_response_id });   my $part_resp = join('_',@{ $part_response_id });
Line 2171  KEYWORDS Line 2597  KEYWORDS
  $seen{$partid}++;   $seen{$partid}++;
  next if ($$handgrade{$part_resp} ne 'yes'    next if ($$handgrade{$part_resp} ne 'yes' 
  && $env{'form.lastSub'} eq 'hdgrade');   && $env{'form.lastSub'} eq 'hdgrade');
  push @partlist,$partid;   push(@partlist,$partid);
  push @gradePartRespid,$partid.'.'.$respid;   push(@gradePartRespid,$partid.'.'.$respid);
  $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));   $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
     }      }
       $request->print(&gradeBox_end()); # </div>
       $request->print('</div>');
   
       $request->print('<div class="LC_grade_info_links">');
       $request->print('</div>');
   
     $result='<input type="hidden" name="partlist'.$counter.      $result='<input type="hidden" name="partlist'.$counter.
  '" value="'.(join ":",@partlist).'" />'."\n";   '" value="'.(join ":",@partlist).'" />'."\n";
     $result.='<input type="hidden" name="gradePartRespid'.      $result.='<input type="hidden" name="gradePartRespid'.
Line 2185  KEYWORDS Line 2617  KEYWORDS
     $partlist[$ctr].'" />'."\n";      $partlist[$ctr].'" />'."\n";
  $ctr++;   $ctr++;
     }      }
     $request->print($result.'</td></tr></table></td></tr></table>'."\n");      $request->print($result.''."\n");
   
 # Done with printing info for one student  # Done with printing info for one student
   
     $request->print('</td></tr></table></p>');      $request->print('</div>');#LC_grade_show_user
   
   
     # print end of form      # print end of form
     if ($counter == $total) {      if ($counter == $total) {
  my $endform='<table border="0"><tr><td>'."\n";          my $endform='<br /><hr /><table border="0"><tr><td>'."\n";
  $endform.='<input type="button" value="Save & Next" '.   $endform.='<input type="button" value="'.&mt('Save &amp; Next').'" '.
     'onClick="javascript:checksubmit(this.form,\'Save & Next\','.      'onclick="javascript:checksubmit(this.form,\'Save & Next\','.
     $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";      $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
  my $ntstu ='<select name="NTSTU">'.   my $ntstu ='<select name="NTSTU">'.
     '<option>1</option><option>2</option>'.      '<option>1</option><option>2</option>'.
Line 2204  KEYWORDS Line 2636  KEYWORDS
     '<option>7</option><option>10</option></select>'."\n";      '<option>7</option><option>10</option></select>'."\n";
  my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');   my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
  $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;   $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
  $endform.=$ntstu.'student(s) &nbsp;&nbsp;';          $endform.=&mt('[_1]student(s)',$ntstu);
  $endform.='<input type="button" value="Previous" '.   $endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('Previous').'" '.
     'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".      'onclick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
     '<input type="button" value="Next" '.      '<input type="button" value="'.&mt('Next').'" '.
     'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';      'onclick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
  $endform.='(Next and Previous (student) do not save the scores.)'."\n" ;          $endform.='<span class="LC_warning">'.
                     &mt('(Next and Previous (student) do not save the scores.)').
                     '</span>'."\n" ;
         $endform.="<input type='hidden' value='".&get_increment().          $endform.="<input type='hidden' value='".&get_increment().
             "' name='increment' />";              "' name='increment' />";
  $endform.='</td><tr></table></form>';   $endform.='</td></tr></table></form>';
  $endform.=&show_grading_menu_form($symb);  
  $request->print($endform);   $request->print($endform);
     }      }
     return '';      return '';
Line 2232  sub check_collaborators { Line 2665  sub check_collaborators {
  next if ($record->{'resource.'.$part.'.collaborators'} eq '');   next if ($record->{'resource.'.$part.'.collaborators'} eq '');
  my (@good_collaborators, @bad_collaborators);   my (@good_collaborators, @bad_collaborators);
  foreach my $possible_collaborator   foreach my $possible_collaborator
     (split(/,?\s+/,$record->{'resource.'.$part.'.collaborators'})) {       (split(/[,;\s]+/,$record->{'resource.'.$part.'.collaborators'})) { 
     $possible_collaborator =~ s/[\$\^\(\)]//g;      $possible_collaborator =~ s/[\$\^\(\)]//g;
     next if ($possible_collaborator eq '');      next if ($possible_collaborator eq '');
     my ($co_name,$co_dom) = split(/\@|:/,$possible_collaborator);      my ($co_name,$co_dom) = split(/:/,$possible_collaborator);
     $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);      $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
     next if ($co_name eq $uname && $co_dom eq $udom);      next if ($co_name eq $uname && $co_dom eq $udom);
     # Doing this grep allows 'fuzzy' specification      # Doing this grep allows 'fuzzy' specification
Line 2248  sub check_collaborators { Line 2681  sub check_collaborators {
     }      }
  }   }
  if (scalar(@good_collaborators) != 0) {   if (scalar(@good_collaborators) != 0) {
     $result.='<b>Collaborators: </b>';      $result.='<br />'.&mt('Collaborators:').'<ol>';
     foreach my $name (@good_collaborators) {      foreach my $name (@good_collaborators) {
  my ($lastname,$givenn) = split(/,/,$$fullname{$name});   my ($lastname,$givenn) = split(/,/,$$fullname{$name});
  push(@col_fullnames, $givenn.' '.$lastname);   push(@col_fullnames, $givenn.' '.$lastname);
  $result.=$fullname->{$name}.'&nbsp; &nbsp; &nbsp;';   $result.='<li>'.$fullname->{$name}.'</li>';
     }      }
     $result.='<br />'."\n";      $result.='</ol><br />'."\n";
     my ($part)=split(/\./,$_);      my ($part)=split(/\./,$part);
     $result.='<input type="hidden" name="collaborator'.$counter.      $result.='<input type="hidden" name="collaborator'.$counter.
  '" value="'.$part.':'.(join ':',@good_collaborators).'" />'.   '" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
  "\n";   "\n";
  }   }
  if (scalar(@bad_collaborators) > 0) {   if (scalar(@bad_collaborators) > 0) {
     $result.='<div class="LC_error">';      $result.='<div class="LC_warning">';
     $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));      $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
     $result .= '</div>';      $result .= '</div>';
  }            }         
  if (scalar(@bad_collaborators > $ncol)) {   if (scalar(@bad_collaborators > $ncol)) {
     $result .= '<div class="LC_error">';      $result .= '<div class="LC_warning">';
     $result .= &mt('This student has submitted too many '.      $result .= &mt('This student has submitted too many '.
  'collaborators.  Maximum is [_1].',$ncol);   'collaborators.  Maximum is [_1].',$ncol);
     $result .= '</div>';      $result .= '</div>';
Line 2277  sub check_collaborators { Line 2710  sub check_collaborators {
   
 #--- Retrieve the last submission for all the parts  #--- Retrieve the last submission for all the parts
 sub get_last_submission {  sub get_last_submission {
     my ($returnhash)=@_;      my ($returnhash,$is_tool)=@_;
     my (@string,$timestamp);      my (@string,$timestamp,%lasthidden);
     if ($$returnhash{'version'}) {      if ($$returnhash{'version'}) {
  my %lasthash=();   my %lasthash=();
  my ($version);   my ($version);
Line 2287  sub get_last_submission { Line 2720  sub get_last_submission {
  $$returnhash{$version.':keys'}))) {   $$returnhash{$version.':keys'}))) {
  $lasthash{$key}=$$returnhash{$version.':'.$key};   $lasthash{$key}=$$returnhash{$version.':'.$key};
  $timestamp =    $timestamp = 
     scalar(localtime($$returnhash{$version.':timestamp'}));      &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
     }      }
  }   }
           my (%typeparts,%randombytry);
           my $showsurv = 
               &Apache::lonnet::allowed('vas',$env{'request.course.id'});
           foreach my $key (sort(keys(%lasthash))) {
               if ($key =~ /\.type$/) {
                   if (($lasthash{$key} eq 'anonsurvey') || 
                       ($lasthash{$key} eq 'anonsurveycred') ||
                       ($lasthash{$key} eq 'randomizetry')) {
                       my ($ign,@parts) = split(/\./,$key);
                       pop(@parts);
                       my $id = join('.',@parts);
                       if ($lasthash{$key} eq 'randomizetry') {
                           $randombytry{$ign.'.'.$id} = $lasthash{$key};
                       } else {
                           unless ($showsurv) {
                               $typeparts{$ign.'.'.$id} = $lasthash{$key};
                           }
                       }
                       delete($lasthash{$key});
                   }
               }
           }
           my @hidden = keys(%typeparts);
           my @randomize = keys(%randombytry);
  foreach my $key (keys(%lasthash)) {   foreach my $key (keys(%lasthash)) {
     next if ($key !~ /\.submission$/);      next if ($key !~ /\.submission$/);
               my $hide;
               if (@hidden) {
                   foreach my $id (@hidden) {
                       if ($key =~ /^\Q$id\E/) {
                           $hide = 'anon';
                           last;
                       }
                   }
               }
               unless ($hide) {
                   if (@randomize) {
                       foreach my $id (@randomize) {
                           if ($key =~ /^\Q$id\E/) {
                               $hide = 'rand';
                               last;
                           }
                       }
                   }
               }
     my ($partid,$foo) = split(/submission$/,$key);      my ($partid,$foo) = split(/submission$/,$key);
     my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?      my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? 1 : 0;
  '<span class="LC_warning">Draft Copy</span> ' : '';              push(@string, join(':', $key, $hide, $draft, (
     push(@string, join(':', $key, $draft.$lasthash{$key}));                  ref($lasthash{$key}) eq 'ARRAY' ?
                       join(',', @{$lasthash{$key}}) : $lasthash{$key}) ));
  }   }
     }      }
     if (!@string) {      if (!@string) {
           my $msg;
           if ($is_tool) {
               $msg = &mt('No grade passed back.');
           } else {
               $msg = &mt('Nothing submitted - no attempts.');
           }
  $string[0] =   $string[0] =
     '<span class="LC_warning">Nothing submitted - no attempts.</span>';      '<span class="LC_warning">'.$msg.'</span>';
     }      }
     return (\@string,\$timestamp);      return (\@string,\$timestamp);
 }  }
Line 2319  sub keywords_highlight { Line 2801  sub keywords_highlight {
     return $string;      return $string;
 }  }
   
   # For Tasks provide a mechanism to display previous version for one specific student
   
   sub show_previous_task_version {
       my ($request,$symb) = @_;
       if ($symb eq '') {
           $request->print(
               '<span class="LC_error">'.
               &mt('Unable to handle ambiguous references.').
               '</span>');
           return '';
       }
       my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'});
       my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
       if (!&canview($usec)) {
           $request->print(
               '<span class="LC_warning">'.
               &mt('Unable to view previous version for requested student.').
               ' '.&mt('([_1] in section [_2] in course id [_3])',
                       $uname.':'.$udom,$usec,$env{'request.course.id'}).
               '</span>');
           return;
       }
       my $mode = 'both';
       my $isTask = ($symb =~/\.task$/);
       if ($isTask) {
           if ($env{'form.previousversion'} =~ /^\d+$/) {
               if ($env{'form.fullname'} eq '') {
                   $env{'form.fullname'} =
                       &Apache::loncommon::plainname($uname,$udom,'lastname');
               }
               my $probtitle=&Apache::lonnet::gettitle($symb);
               $request->print("\n\n".
                               '<div class="LC_grade_show_user">'.
                               '<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
                               '</h2>'."\n");
               &Apache::lonxml::clear_problem_counter();
               $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,
                               {'previousversion' => $env{'form.previousversion'} }));
               $request->print("\n</div>");
           }
       }
       return;
   }
   
   sub choose_task_version_form {
       my ($symb,$uname,$udom,$nomenu) = @_;
       my $isTask = ($symb =~/\.task$/);
       my ($current,$version,$result,$js,$displayed,$rowtitle);
       if ($isTask) {
           my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
                                                 $udom,$uname);
           if (($record{'resource.0.version'} eq '') ||
               ($record{'resource.0.version'} < 2)) {
               return ($record{'resource.0.version'},
                       $record{'resource.0.version'},$result,$js);
           } else {
               $current = $record{'resource.0.version'};
           }
           if ($env{'form.previousversion'}) {
               $displayed = $env{'form.previousversion'};
               $rowtitle = &mt('Choose another version:')
           } else {
               $displayed = $current;
               $rowtitle = &mt('Show earlier version:');
           }
           $result = '<div class="LC_left_float">';
           my $list;
           my $numversions = 0;
           for (my $i=1; $i<=$record{'resource.0.version'}; $i++) {
               if ($i == $current) {
                   if (!$env{'form.previousversion'} || $nomenu) {
                       next;
                   } else {
                       $list .= '<option value="'.$i.'">'.&mt('Current').'</option>'."\n";
                       $numversions ++;
                   }
               } elsif (defined($record{'resource.'.$i.'.0.status'})) {
                   unless ($i == $env{'form.previousversion'}) {
                       $numversions ++;
                   }
                   $list .= '<option value="'.$i.'">'.$i.'</option>'."\n";
               }
           }
           if ($numversions) {
               $symb = &HTML::Entities::encode($symb,'<>"&');
               $result .=
                   '<form name="getprev" method="post" action=""'.
                   ' onsubmit="return previousVersion('."'$uname','$udom','$symb','$displayed'".');">'.
                   &Apache::loncommon::start_data_table().
                   &Apache::loncommon::start_data_table_row().
                   '<th align="left">'.$rowtitle.'</th>'.
                   '<td><select name="version">'.
                   '<option>'.&mt('Select').'</option>'.
                   $list.
                   '</select></td>'.
                   &Apache::loncommon::end_data_table_row();
               unless ($nomenu) {
                   $result .= &Apache::loncommon::start_data_table_row().
                   '<th align="left">'.&mt('Open in new window').'</th>'.
                   '<td><span class="LC_nobreak">'.
                   '<label><input type="radio" name="prevwin" value="1" />'.
                   &mt('Yes').'</label>'.
                   '<label><input type="radio" name="prevwin" value="0" checked="checked" />'.&mt('No').'</label>'.
                   '</span></td>'.
                   &Apache::loncommon::end_data_table_row();
               }
               $result .=
                   &Apache::loncommon::start_data_table_row().
                   '<th align="left">&nbsp;</th>'.
                   '<td>'.
                   '<input type="submit" name="prevsub" value="'.&mt('Display').'" />'.
                   '</td>'.
                   &Apache::loncommon::end_data_table_row().
                   &Apache::loncommon::end_data_table().
                   '</form>';
               $js = &previous_display_javascript($nomenu,$current);
           } elsif ($displayed && $nomenu) {
               $result .= '<a href="javascript:window.close()">'.&mt('Close window').'</a>';
           } else {
               $result .= &mt('No previous versions to show for this student');
           }
           $result .= '</div>';
       }
       return ($current,$displayed,$result,$js);
   }
   
   sub previous_display_javascript {
       my ($nomenu,$current) = @_;
       my $js = <<"JSONE";
   <script type="text/javascript">
   // <![CDATA[
   function previousVersion(uname,udom,symb) {
       var current = '$current';
       var version = document.getprev.version.options[document.getprev.version.selectedIndex].value;
       var prevstr = new RegExp("^\\\\d+\$");
       if (!prevstr.test(version)) {
           return false;
       }
       var url = '';
       if (version == current) {
           url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=submission';
       } else {
           url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=versionsub&previousversion='+version;
       }
   JSONE
       if ($nomenu) {
           $js .= <<"JSTWO";
       document.location.href = url;
   JSTWO
       } else {
           $js .= <<"JSTHREE";
       var newwin = 0;
       for (var i=0; i<document.getprev.prevwin.length; i++) {
           if (document.getprev.prevwin[i].checked == true) {
               newwin = document.getprev.prevwin[i].value;
           }
       }
       if (newwin == 1) {
           var options = 'height=600,width=800,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no';
           url = url+'&inhibitmenu=yes';
           if (typeof(previousWin) == 'undefined' || previousWin.closed) {
               previousWin = window.open(url,'',options,1);
           } else {
               previousWin.location.href = url;
           }
           previousWin.focus();
           return false;
       } else {
           document.location.href = url;
           return false;
       }
   JSTHREE
       }
       $js .= <<"ENDJS";
       return false;
   }
   // ]]>
   </script>
   ENDJS
   
   }
   
 #--- Called from submission routine  #--- Called from submission routine
 sub processHandGrade {  sub processHandGrade {
     my ($request) = shift;      my ($request,$symb) = @_;
     my $symb   = &get_symb($request);  
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     my $button = $env{'form.gradeOpt'};      my $button = $env{'form.gradeOpt'};
     my $ngrade = $env{'form.NCT'};      my $ngrade = $env{'form.NCT'};
Line 2334  sub processHandGrade { Line 2997  sub processHandGrade {
  my $ctr = 0;   my $ctr = 0;
  while ($ctr < $ngrade) {   while ($ctr < $ngrade) {
     my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});      my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
     my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr);      my ($errorflag,$pts,$wgt,$numhidden) = 
                   &saveHandGrade($request,$symb,$uname,$udom,$ctr);
     if ($errorflag eq 'no_score') {      if ($errorflag eq 'no_score') {
  $ctr++;   $ctr++;
  next;   next;
     }      }
     if ($errorflag eq 'not_allowed') {      if ($errorflag eq 'not_allowed') {
  $request->print("<span class=\"LC_warning\">Not allowed to modify grades for $uname:$udom</span>");   $request->print(
                       '<span class="LC_error">'
                      .&mt('Not allowed to modify grades for [_1]',"$uname:$udom")
                      .'</span>');
  $ctr++;   $ctr++;
  next;   next;
     }      }
               if ($numhidden) {
                   $request->print(
                       '<span class="LC_info">'
                      .&mt('For [_1]: [quant,_2,transaction] hidden',"$uname:$udom",$numhidden)
                      .'</span><br />');
               }
     my $includemsg = $env{'form.includemsg'.$ctr};      my $includemsg = $env{'form.includemsg'.$ctr};
     my ($subject,$message,$msgstatus) = ('','','');      my ($subject,$message,$msgstatus) = ('','','');
     my $restitle = &Apache::lonnet::gettitle($symb);      my $restitle = &Apache::lonnet::gettitle($symb);
Line 2362  sub processHandGrade { Line 3035  sub processHandGrade {
  if ($env{'form.withgrades'.$ctr}) {   if ($env{'form.withgrades'.$ctr}) {
     $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;      $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
     $messagetail = " for <a href=\"".      $messagetail = " for <a href=\"".
                    $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";                     $feedurl."?symb=$showsymb\">$restitle</a>";
  }   }
  $msgstatus =    $msgstatus = 
                     &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,                      &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
Line 2370  sub processHandGrade { Line 3043  sub processHandGrade {
                                                      undef,$feedurl,undef,                                                       undef,$feedurl,undef,
                                                      undef,undef,$showsymb,                                                       undef,undef,$showsymb,
                                                      $restitle);                                                       $restitle);
  $request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.   $request->print('<br />'.&mt('Sending message to [_1]',$uname.':'.$udom).': '.
  $msgstatus);   $msgstatus.'<br />');
     }      }
     if ($env{'form.collaborator'.$ctr}) {      if ($env{'form.collaborator'.$ctr}) {
  my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");   my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
Line 2390  sub processHandGrade { Line 3063  sub processHandGrade {
       $udom);        $udom);
     if ($env{'form.withgrades'.$ctr}) {      if ($env{'form.withgrades'.$ctr}) {
  $messagetail = " for <a href=\"".   $messagetail = " for <a href=\"".
                                     $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";                                      $baseurl."?symb=$showsymb\">$restitle</a>";
     }      }
     $msgstatus =       $msgstatus = 
  &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);   &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
Line 2402  sub processHandGrade { Line 3075  sub processHandGrade {
  }   }
     }      }
   
     if ($env{'form.handgrade'} eq 'yes') {  #    if ($env{'form.handgrade'} eq 'yes') {
       if (1) {
  # Keywords sorted in alphabatical order   # Keywords sorted in alphabatical order
  my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};   my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
  my %keyhash = ();   my %keyhash = ();
Line 2455  sub processHandGrade { Line 3129  sub processHandGrade {
     my $processUser = $env{'form.unamedom'.$ctr};      my $processUser = $env{'form.unamedom'.$ctr};
     ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);      ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
     $env{'form.fullname'} = $$fullname{$processUser};      $env{'form.fullname'} = $$fullname{$processUser};
     &submission($request,$ctr,$total-1);      &submission($request,$ctr,$total-1,$symb);
     $ctr++;      $ctr++;
  }   }
  return '';   return '';
     }      }
   
 # Go directly to grade student - from submission or link from chart page  
     if ($button eq 'Grade Student') {  
  (undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($symb);  
  my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};  
  ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);  
  $env{'form.fullname'} = $$fullname{$processUser};  
  &submission($request,0,0);  
  return '';  
     }  
   
     # Get the next/previous one or group of students      # Get the next/previous one or group of students
     my $firststu = $env{'form.unamedom0'};      my $firststu = $env{'form.unamedom0'};
     my $laststu = $env{'form.unamedom'.($ngrade-1)};      my $laststu = $env{'form.unamedom'.($ngrade-1)};
Line 2483  sub processHandGrade { Line 3147  sub processHandGrade {
   
     my (@parsedlist,@nextlist);      my (@parsedlist,@nextlist);
     my ($nextflg) = 0;      my ($nextflg) = 0;
     foreach (sort       foreach my $item (sort 
      {       {
  if (lc($$fullname{$a}) ne lc($$fullname{$b})) {   if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
      return (lc($$fullname{$a}) cmp lc($$fullname{$b}));       return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
  }   }
  return $a cmp $b;   return $a cmp $b;
      } (keys(%$fullname))) {       } (keys(%$fullname))) {
   # FIXME: this is fishy, looks like the button label
  if ($nextflg == 1 && $button =~ /Next$/) {   if ($nextflg == 1 && $button =~ /Next$/) {
     push @parsedlist,$_;      push(@parsedlist,$item);
  }   }
  $nextflg = 1 if ($_ eq $laststu);   $nextflg = 1 if ($item eq $laststu);
  if ($button eq 'Previous') {   if ($button eq 'Previous') {
     last if ($_ eq $firststu);      last if ($item eq $firststu);
     push @parsedlist,$_;      push(@parsedlist,$item);
  }   }
     }      }
     $ctr = 0;      $ctr = 0;
   # FIXME: this is fishy, looks like the button label
     @parsedlist = reverse @parsedlist if ($button eq 'Previous');      @parsedlist = reverse @parsedlist if ($button eq 'Previous');
     my ($partlist) = &response_type($symb);      my $res_error;
       my ($partlist) = &response_type($symb,\$res_error);
       if ($res_error) {
           $request->print(&navmap_errormsg());
           return;
       }
     foreach my $student (@parsedlist) {      foreach my $student (@parsedlist) {
  my $submitonly=$env{'form.submitonly'};   my $submitonly=$env{'form.submitonly'};
  my ($uname,$udom) = split(/:/,$student);   my ($uname,$udom) = split(/:/,$student);
Line 2519  sub processHandGrade { Line 3190  sub processHandGrade {
     my $submitted = 0;      my $submitted = 0;
     my $ungraded = 0;      my $ungraded = 0;
     my $incorrect = 0;      my $incorrect = 0;
     foreach (keys(%status)) {      foreach my $item (keys(%status)) {
  $submitted = 1 if ($status{$_} ne 'nothing');   $submitted = 1 if ($status{$item} ne 'nothing');
  $ungraded = 1 if ($status{$_} =~ /^ungraded/);   $ungraded = 1 if ($status{$item} =~ /^ungraded/);
  $incorrect = 1 if ($status{$_} =~ /^incorrect/);   $incorrect = 1 if ($status{$item} =~ /^incorrect/);
  my ($foo,$partid,$foo1) = split(/\./,$_);   my ($foo,$partid,$foo1) = split(/\./,$item);
  if ($status{'resource.'.$partid.'.submitted_by'} ne '') {   if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
     $submitted = 0;      $submitted = 0;
  }   }
Line 2534  sub processHandGrade { Line 3205  sub processHandGrade {
     next if (!$ungraded && ($submitonly eq 'graded'));      next if (!$ungraded && ($submitonly eq 'graded'));
     next if (!$incorrect && $submitonly eq 'incorrect');      next if (!$incorrect && $submitonly eq 'incorrect');
  }   }
  push @nextlist,$student if ($ctr < $ntstu);   push(@nextlist,$student) if ($ctr < $ntstu);
  last if ($ctr == $ntstu);   last if ($ctr == $ntstu);
  $ctr++;   $ctr++;
     }      }
Line 2542  sub processHandGrade { Line 3213  sub processHandGrade {
     $ctr = 0;      $ctr = 0;
     my $total = scalar(@nextlist)-1;      my $total = scalar(@nextlist)-1;
   
     foreach (sort @nextlist) {      foreach (sort(@nextlist)) {
  my ($uname,$udom,$submitter) = split(/:/);   my ($uname,$udom,$submitter) = split(/:/);
  $env{'form.student'}  = $uname;   $env{'form.student'}  = $uname;
  $env{'form.userdom'}  = $udom;   $env{'form.userdom'}  = $udom;
  $env{'form.fullname'} = $$fullname{$_};   $env{'form.fullname'} = $$fullname{$_};
  &submission($request,$ctr,$total);   &submission($request,$ctr,$total,$symb);
  $ctr++;   $ctr++;
     }      }
     if ($total < 0) {      if ($total < 0) {
  my $the_end = '<h3><span class="LC_info">LON-CAPA User Message</span></h3><br />'."\n";   my $the_end.='<p>'.&mt('[_1]Message:[_2] No more students for this section or class.','<b>','</b>').'</p>'."\n";
  $the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n";  
  $the_end.='Click on the button below to return to the grading menu.<br /><br />'."\n";  
  $the_end.=&show_grading_menu_form($symb);  
  $request->print($the_end);   $request->print($the_end);
     }      }
     return '';      return '';
Line 2570  sub saveHandGrade { Line 3238  sub saveHandGrade {
     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);      my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
     my @parts_graded;      my @parts_graded;
     my %newrecord  = ();      my %newrecord  = ();
     my ($pts,$wgt) = ('','');      my ($pts,$wgt,$totchg) = ('','',0);
     my %aggregate = ();      my %aggregate = ();
     my $aggregateflag = 0;      my $aggregateflag = 0;
       if ($env{'form.HIDE'.$newflg}) {
           my ($version,$parts) = split(/:/,$env{'form.HIDE'.$newflg},2);
           my $numchgs = &makehidden($version,$parts,\%record,$symb,$domain,$stuname,1);
           $totchg += $numchgs;
       }
     my @parts = split(/:/,$env{'form.partlist'.$newflg});      my @parts = split(/:/,$env{'form.partlist'.$newflg});
     foreach my $new_part (@parts) {      foreach my $new_part (@parts) {
  #collaborator ($submi may vary for different parts   #collaborator ($submi may vary for different parts
Line 2588  sub saveHandGrade { Line 3261  sub saveHandGrade {
     }      }
  } elsif ($dropMenu eq 'reset status'   } elsif ($dropMenu eq 'reset status'
  && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts   && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
     foreach my $key (keys (%record)) {      foreach my $key (keys(%record)) {
  if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }   if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
     }      }
     $newrecord{'resource.'.$new_part.'.regrader'}=      $newrecord{'resource.'.$new_part.'.regrader'}=
Line 2623  sub saveHandGrade { Line 3296  sub saveHandGrade {
                 &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);                  &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
  next;   next;
     } else {      } else {
         push @parts_graded, $new_part;          push(@parts_graded,$new_part);
     }      }
     if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {      if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
  $newrecord{'resource.'.$new_part.'.awarded'}  = $partial;   $newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
Line 2650  sub saveHandGrade { Line 3323  sub saveHandGrade {
         $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||          $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
         $dropMenu eq 'reset status')          $dropMenu eq 'reset status')
    {     {
     push (@version_parts,$new_part);      push(@version_parts,$new_part);
  }   }
     }      }
     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
Line 2675  sub saveHandGrade { Line 3348  sub saveHandGrade {
         &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,          &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
       $cdom,$cnum);        $cdom,$cnum);
     }      }
     return ('',$pts,$wgt);      return ('',$pts,$wgt,$totchg);
   }
   
   sub makehidden {
       my ($version,$parts,$record,$symb,$domain,$stuname,$tolog) = @_;
       return unless (ref($record) eq 'HASH');
       my %modified;
       my $numchanged = 0;
       if (exists($record->{$version.':keys'})) {
           my $partsregexp = $parts;
           $partsregexp =~ s/,/|/g;
           foreach my $key (split(/\:/,$record->{$version.':keys'})) {
               if ($key =~ /^resource\.(?:$partsregexp)\.([^\.]+)$/) {
                    my $item = $1;
                    unless (($item eq 'solved') || ($item =~ /^award(|msg|ed)$/)) {
                        $modified{$key} = $record->{$version.':'.$key};
                    }
               } elsif ($key =~ m{^(resource\.(?:$partsregexp)\.[^\.]+\.)(.+)$}) {
                   $modified{$1.'hidden'.$2} = $record->{$version.':'.$key};
               } elsif ($key =~ /^(ip|timestamp|host)$/) {
                   $modified{$key} = $record->{$version.':'.$key};
               }
           }
           if (keys(%modified)) {
               if (&Apache::lonnet::putstore($env{'request.course.id'},$symb,$version,\%modified,
                                             $domain,$stuname,$tolog) eq 'ok') {
                   $numchanged ++;
               }
           }
       }
       return $numchanged;
 }  }
   
 sub check_and_remove_from_queue {  sub check_and_remove_from_queue {
Line 2698  sub check_and_remove_from_queue { Line 3401  sub check_and_remove_from_queue {
   
 sub handback_files {  sub handback_files {
     my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;      my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
     my $portfolio_root = &propath($domain,$stuname).'/userfiles/portfolio';      my $portfolio_root = '/userfiles/portfolio';
     my ($partlist,$handgrade,$responseType) = &response_type($symb);      my $res_error;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
       if ($res_error) {
           $request->print('<br />'.&navmap_errormsg().'<br />');
           return;
       }
       my @handedback;
       my $file_msg;
     my @part_response_id = &flatten_responseType($responseType);      my @part_response_id = &flatten_responseType($responseType);
     foreach my $part_response_id (@part_response_id) {      foreach my $part_response_id (@part_response_id) {
     my ($part_id,$resp_id) = @{ $part_response_id };      my ($part_id,$resp_id) = @{ $part_response_id };
  my $part_resp = join('_',@{ $part_response_id });   my $part_resp = join('_',@{ $part_response_id });
             if (($env{'form.'.$newflg.'_'.$part_resp.'_returndoc1'}) && ($new_part == $part_id)) {          if (($env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'} =~ /^\d+$/) & ($new_part eq $part_id)) {
                 # if multiple files are uploaded names will be 'returndoc2','returndoc3'              for (my $counter=1; $counter<=$env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'}; $counter++) {
                 my $file_counter = 1;                  # if multiple files are uploaded names will be 'returndoc2','returndoc3' 
  my $file_msg;                  if ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter}) {
                 while ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter}) {                      my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter.'.filename'};
                     my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'.filename'};  
                     my ($directory,$answer_file) =                       my ($directory,$answer_file) = 
                         ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter} =~ /^(.*?)([^\/]*)$/);                          ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter} =~ /^(.*?)([^\/]*)$/);
                     my ($answer_name,$answer_ver,$answer_ext) =                      my ($answer_name,$answer_ver,$answer_ext) =
         &file_name_version_ext($answer_file);          &Apache::lonnet::file_name_version_ext($answer_file);
     my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);      my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
     my @dir_list = &Apache::lonnet::dirlist($portfolio_path,$domain,$stuname,$portfolio_root);                      my $getpropath = 1;
     my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);                      my ($dir_list,$listerror) = 
                     # fix file name                          &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,
                                                    $domain,$stuname,$getpropath);
       my $version = &Apache::lonnet::get_next_version($answer_name,$answer_ext,$dir_list);
                       # fix filename
                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);                      my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
                     my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,                      my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
                                            $newflg.'_'.$part_resp.'_returndoc'.$file_counter,                                             $newflg.'_'.$part_resp.'_returndoc'.$counter,
                                            $save_file_name);                                             $save_file_name);
                     if ($result !~ m|^/uploaded/|) {                      if ($result !~ m|^/uploaded/|) {
                         $request->print('<span class="LC_error">An error occurred ('.$result.                          $request->print('<br /><span class="LC_error">'.
                         ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</span><br />');                              &mt('An error occurred ([_1]) while trying to upload [_2].',
                                   $result,$newflg.'_'.$part_resp.'_returndoc'.$counter).
                                           '</span>');
                     } else {                      } else {
                         # mark the file as read only                          # mark the file as read only
                         my @files = ($save_file_name);                          push(@handedback,$save_file_name);
                         my @what = ($symb,$env{'request.course.id'},'handback');  
                         &Apache::lonnet::mark_as_readonly($domain,$stuname,\@files,\@what);  
  if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {   if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {
     $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';      $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
  }   }
                         $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;                          $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;
  $file_msg.= "\n".'<br /><span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span><br />";   $file_msg.= '<span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span> <br />";
   
                     }                      }
                     $request->print("<br />".$fname." will be the uploaded file name");                      $request->print('<br />'.&mt('[_1] will be the uploaded filename [_2]','<span class="LC_info">'.$fname.'</span>','<span class="LC_filename">'.$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter}.'</span>'));
                     $request->print(" ".$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter});                  }
                     $file_counter++;  
                 }  
  my $subject = "File Handed Back by Instructor ";  
  my $message = "A file has been returned that was originally submitted in reponse to: <br />";  
  $message .= "<strong>".&Apache::lonnet::gettitle($symb)."</strong><br />";  
  $message .= ' The returned file(s) are named: '. $file_msg;  
  $message .= " and can be found in your portfolio space.";  
  my ($feedurl,$showsymb) =   
     &get_feedurl_and_symb($symb,$domain,$stuname);  
                 my $restitle = &Apache::lonnet::gettitle($symb);  
  my $msgstatus =   
                    &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject.  
  ' (File Returned) ['.$restitle.']',$message,undef,  
                          $feedurl,undef,undef,undef,$showsymb,$restitle);  
             }              }
         }          }
       }
       if (@handedback > 0) {
           $request->print('<br />');
           my @what = ($symb,$env{'request.course.id'},'handback');
           &Apache::lonnet::mark_as_readonly($domain,$stuname,\@handedback,\@what);
           my $user_lh = &Apache::loncommon::user_lang($stuname,$domain,$env{'request.course.id'});    
           my ($subject,$message);
           if (scalar(@handedback) == 1) {
               $subject = &mt_user($user_lh,'File Handed Back by Instructor');
               $message = &mt_user($user_lh,'A file has been returned that was originally submitted in response to: ');
           } else {
               $subject = &mt_user($user_lh,'Files Handed Back by Instructor');
               $message = &mt_user($user_lh,'Files have been returned that were originally submitted in response to: ');
           }
           $message .= "<p><strong>".&Apache::lonnet::gettitle($symb)." </strong></p>";
           $message .= &mt_user($user_lh,'The returned file(s) are named: [_1]',"<br />$file_msg <br />").
                       &mt_user($user_lh,'The file(s) can be found in your [_1]portfolio[_2].','<a href="/adm/portfolio">','</a>');
           my ($feedurl,$showsymb) =
               &get_feedurl_and_symb($symb,$domain,$stuname);
           my $restitle = &Apache::lonnet::gettitle($symb);
           $subject .= ' '.&mt_user($user_lh,'(File Returned)').' ['.$restitle.']';
           my $msgstatus =
                &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject,
                    $message,undef,$feedurl,undef,undef,undef,$showsymb,
                    $restitle);
           if ($msgstatus) {
               $request->print(&mt('Notification message status: [_1]','<span class="LC_info">'.$msgstatus.'</span>').'<br />');
           }
       }
     return;      return;
 }  }
   
Line 2822  sub decrement_aggs { Line 3547  sub decrement_aggs {
     if ($aggtries == $totaltries) {      if ($aggtries == $totaltries) {
         $decrement{'users'} = 1;          $decrement{'users'} = 1;
     }      }
     foreach my $type (keys (%decrement)) {      foreach my $type (keys(%decrement)) {
         $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};          $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
     }      }
     return;      return;
Line 2852  sub version_portfiles { Line 3577  sub version_portfiles {
     my $version_parts = join('|',@$v_flag);      my $version_parts = join('|',@$v_flag);
     my @returned_keys;      my @returned_keys;
     my $parts = join('|', @$parts_graded);      my $parts = join('|', @$parts_graded);
     my $portfolio_root = &propath($domain,$stu_name).  
  '/userfiles/portfolio';  
     foreach my $key (keys(%$record)) {      foreach my $key (keys(%$record)) {
         my $new_portfiles;          my $new_portfiles;
         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {          if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
             my @versioned_portfiles;              my @versioned_portfiles;
             my @portfiles = split(/\s*,\s*/,$$record{$key});              my @portfiles = split(/\s*,\s*/,$$record{$key});
             foreach my $file (@portfiles) {              if (@portfiles) {
                 &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);                  &Apache::lonnet::portfiles_versioning($symb,$domain,$stu_name,\@portfiles,
                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);                                                        \@versioned_portfiles);
  my ($answer_name,$answer_ver,$answer_ext) =  
     &file_name_version_ext($answer_file);  
                 my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);  
                 my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);  
                 my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);  
                 if ($new_answer ne 'problem getting file') {  
                     push(@versioned_portfiles, $directory.$new_answer);  
                     &Apache::lonnet::mark_as_readonly($domain,$stu_name,  
                         [$directory.$new_answer],  
                         [$symb,$env{'request.course.id'},'graded']);  
                 }  
             }              }
             $$record{$key} = join(',',@versioned_portfiles);              $$record{$key} = join(',',@versioned_portfiles);
             push(@returned_keys,$key);              push(@returned_keys,$key);
Line 2881  sub version_portfiles { Line 3593  sub version_portfiles {
     return (@returned_keys);         return (@returned_keys);   
 }  }
   
 sub get_next_version {  
     my ($answer_name, $answer_ext, $dir_list) = @_;  
     my $version;  
     foreach my $row (@$dir_list) {  
         my ($file) = split(/\&/,$row,2);  
         my ($file_name,$file_version,$file_ext) =  
     &file_name_version_ext($file);  
         if (($file_name eq $answer_name) &&   
     ($file_ext eq $answer_ext)) {  
                 # gets here if filename and extension match, regardless of version  
                 if ($file_version ne '') {  
                 # a versioned file is found  so save it for later  
                 if ($file_version > $version) {  
     $version = $file_version;  
         }  
             }  
         }  
     }   
     $version ++;  
     return($version);  
 }  
   
 sub version_selected_portfile {  
     my ($domain,$stu_name,$directory,$file_name,$version) = @_;  
     my ($answer_name,$answer_ver,$answer_ext) =  
         &file_name_version_ext($file_name);  
     my $new_answer;  
     $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");  
     if($env{'form.copy'} eq '-1') {  
         $new_answer = 'problem getting file';  
     } else {  
         $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;  
         my $copy_result = &Apache::lonnet::finishuserfileupload(  
                             $stu_name,$domain,'copy',  
         '/portfolio'.$directory.$new_answer);  
     }      
     return ($new_answer);  
 }  
   
 sub file_name_version_ext {  
     my ($file)=@_;  
     my @file_parts = split(/\./, $file);  
     my ($name,$version,$ext);  
     if (@file_parts > 1) {  
  $ext=pop(@file_parts);  
  if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) {  
     $version=pop(@file_parts);  
  }  
  $name=join('.',@file_parts);  
     } else {  
  $name=join('.',@file_parts);  
     }  
     return($name,$version,$ext);  
 }  
   
 #--------------------------------------------------------------------------------------  #--------------------------------------------------------------------------------------
 #  #
 #-------------------------- Next few routines handles grading by section or whole class  #-------------------------- Next few routines handles grading by section or whole class
Line 2944  sub file_name_version_ext { Line 3601  sub file_name_version_ext {
 sub viewgrades_js {  sub viewgrades_js {
     my ($request) = shift;      my ($request) = shift;
   
     $request->print(<<VIEWJAVASCRIPT);      my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
 <script type="text/javascript" language="javascript">      &js_escape(\$alertmsg);
       $request->print(&Apache::lonhtmlcommon::scripttag(<<VIEWJAVASCRIPT));
    function writePoint(partid,weight,point) {     function writePoint(partid,weight,point) {
  var radioButton = document.classgrade["RADVAL_"+partid];   var radioButton = document.classgrade["RADVAL_"+partid];
  var textbox = document.classgrade["TEXTVAL_"+partid];   var textbox = document.classgrade["TEXTVAL_"+partid];
  if (point == "textval") {   if (point == "textval") {
     point = document.classgrade["TEXTVAL_"+partid].value;      point = document.classgrade["TEXTVAL_"+partid].value;
     if (isNaN(point) || parseFloat(point) < 0) {      if (isNaN(point) || parseFloat(point) < 0) {
  alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));   alert("$alertmsg"+parseFloat(point));
  var resetbox = false;   var resetbox = false;
  for (var i=0; i<radioButton.length; i++) {   for (var i=0; i<radioButton.length; i++) {
     if (radioButton[i].checked) {      if (radioButton[i].checked) {
Line 3050  sub viewgrades_js { Line 3708  sub viewgrades_js {
  var weight = document.classgrade["weight_"+partid].value;   var weight = document.classgrade["weight_"+partid].value;
   
  if (isNaN(point) || parseFloat(point) < 0) {   if (isNaN(point) || parseFloat(point) < 0) {
     alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));      alert("$alertmsg"+parseFloat(point));
     textbox.value = "";      textbox.value = "";
     return;      return;
  }   }
Line 3106  sub viewgrades_js { Line 3764  sub viewgrades_js {
  }   }
     }      }
   
 </script>  
 VIEWJAVASCRIPT  VIEWJAVASCRIPT
 }  }
   
 #--- show scores for a section or whole class w/ option to change/update a score  #--- show scores for a section or whole class w/ option to change/update a score
 sub viewgrades {  sub viewgrades {
     my ($request) = shift;      my ($request,$symb) = @_;
       my ($is_tool,$toolsymb);
       if ($symb =~ /ext\.tool$/) {
           $is_tool = 1;
           $toolsymb = $symb;
       }
     &viewgrades_js($request);      &viewgrades_js($request);
   
     my ($symb) = &get_symb($request);  
     #need to make sure we have the correct data for later EXT calls,       #need to make sure we have the correct data for later EXT calls, 
     #thus invalidate the cache      #thus invalidate the cache
     &Apache::lonnet::devalidatecourseresdata(      &Apache::lonnet::devalidatecourseresdata(
Line 3124  sub viewgrades { Line 3785  sub viewgrades {
     &Apache::lonnet::clear_EXT_cache_status();      &Apache::lonnet::clear_EXT_cache_status();
   
     my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';      my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
     $result.='<h4><b>Current Resource: </b>'.$env{'form.probTitle'}.'</h4>'."\n";  
   
     #view individual student submission form - called using Javascript viewOneStudent      #view individual student submission form - called using Javascript viewOneStudent
     $result.=&jscriptNform($symb);      $result.=&jscriptNform($symb);
Line 3135  sub viewgrades { Line 3795  sub viewgrades {
  '<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".
  &build_section_inputs().   &build_section_inputs().
  '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".  
  '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".   '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
  '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";  
   
     my $sectionClass;      #retrieve selected groups
     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));      my (@groups,$group_display);
     if ($env{'form.section'} eq 'all') {      @groups = &Apache::loncommon::get_env_multiple('form.group');
  $sectionClass='Class </h3>';      if (grep(/^all$/,@groups)) {
     } elsif ($env{'form.section'} eq 'none') {          @groups = ('all');
  $sectionClass=&mt('Students in no Section').'</h3>';      } elsif (grep(/^none$/,@groups)) {
     } else {          @groups = ('none');
  $sectionClass=&mt('Students in Section(s) [_1]',$section_display).'</h3>';      } elsif (@groups > 0) {
     }          $group_display = join(', ',@groups);
     $result.='<h3>'.&mt('Assign Common Grade To [_1]',$sectionClass);      }
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".  
  '<table border=0><tr bgcolor="#ffffdd"><td>';      my ($common_header,$specific_header,@sections,$section_display);
       @sections = &Apache::loncommon::get_env_multiple('form.section');
       if (grep(/^all$/,@sections)) {
           @sections = ('all');
           if ($group_display) {
               $common_header = &mt('Assign Common Grade to Students in Group(s) [_1]',$group_display);
               $specific_header = &mt('Assign Grade to Specific Students in Group(s) [_1]',$group_display);
           } elsif (grep(/^none$/,@groups)) {
               $common_header = &mt('Assign Common Grade to Students not assigned to any groups');
               $specific_header = &mt('Assign Grade to Specific Students not assigned to any groups');
           } else {
       $common_header = &mt('Assign Common Grade to Class');
               $specific_header = &mt('Assign Grade to Specific Students in Class');
           }
       } elsif (grep(/^none$/,@sections)) {
           @sections = ('none');
           if ($group_display) {
               $common_header = &mt('Assign Common Grade to Students in no Section and in Group(s) [_1]',$group_display);
               $specific_header = &mt('Assign Grade to Specific Students in no Section and in Group(s)',$group_display);
           } elsif (grep(/^none$/,@groups)) {
               $common_header = &mt('Assign Common Grade to Students in no Section and in no Group');
               $specific_header = &mt('Assign Grade to Specific Students in no Section and in no Group');
           } else {
               $common_header = &mt('Assign Common Grade to Students in no Section');
       $specific_header = &mt('Assign Grade to Specific Students in no Section');
           }
       } else {
           $section_display = join (", ",@sections);
           if ($group_display) {
               $common_header = &mt('Assign Common Grade to Students in Section(s) [_1], and in Group(s) [_2]',
                                    $section_display,$group_display);
               $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1], and in Group(s) [_2]',
                                      $section_display,$group_display);
           } elsif (grep(/^none$/,@groups)) {
               $common_header = &mt('Assign Common Grade to Students in Section(s) [_1] and no Group',$section_display);
               $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1] and no Group',$section_display);
           } else {
               $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
       $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
           }
       }
       my %submit_types = &substatus_options();
       my $submission_status = $submit_types{$env{'form.submitonly'}};
   
       if ($env{'form.submitonly'} eq 'all') {
           $result.= '<h3>'.$common_header.'</h3>';
       } else {
           my $text;
           if ($is_tool) {
               $text = &mt('(transaction status: "[_1]")',$submission_status);
           } else {
               $text = &mt('(submission status: "[_1]")',$submission_status);
           }
           $result.= '<h3>'.$common_header.'&nbsp;'.$text.'</h3>';
       }
       $result .= &Apache::loncommon::start_data_table();
     #radio buttons/text box for assigning points for a section or class.      #radio buttons/text box for assigning points for a section or class.
     #handles different parts of a problem      #handles different parts of a problem
     my ($partlist,$handgrade,$responseType) = &response_type($symb);      my $res_error;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
       if ($res_error) {
           return &navmap_errormsg();
       }
     my %weight = ();      my %weight = ();
     my $ctsparts = 0;      my $ctsparts = 0;
     $result.='<table border="0">';  
     my %seen = ();      my %seen = ();
     my @part_response_id = &flatten_responseType($responseType);      my @part_response_id;
       if ($is_tool) {
           @part_response_id = ([0,'']);
       } else {
           @part_response_id = &flatten_responseType($responseType);
       }
     foreach my $part_response_id (@part_response_id) {      foreach my $part_response_id (@part_response_id) {
     my ($partid,$respid) = @{ $part_response_id };      my ($partid,$respid) = @{ $part_response_id };
  my $part_resp = join('_',@{ $part_response_id });   my $part_resp = join('_',@{ $part_response_id });
  next if $seen{$partid};   next if $seen{$partid};
  $seen{$partid}++;   $seen{$partid}++;
  my $handgrade=$$handgrade{$part_resp};  # my $handgrade=$$handgrade{$part_resp};
  my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);   my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
  $weight{$partid} = $wgt eq '' ? '1' : $wgt;   $weight{$partid} = $wgt eq '' ? '1' : $wgt;
   
  $result.='<input type="hidden" name="partid_'.  
     $ctsparts.'" value="'.$partid.'" />'."\n";  
  $result.='<input type="hidden" name="weight_'.  
     $partid.'" value="'.$weight{$partid}.'" />'."\n";  
  my $display_part=&get_display_part($partid,$symb);   my $display_part=&get_display_part($partid,$symb);
  $result.='<tr><td><b>Part:</b> '.$display_part.'&nbsp; &nbsp;<b>Point:</b> </td><td>';   my $radio.='<table border="0"><tr>';  
  $result.='<table border="0"><tr>';    
  my $ctr = 0;   my $ctr = 0;
  while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across   while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
     $result.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.      $radio.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.
  'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.   'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
  ','.$ctr.')" />'.$ctr."</label></td>\n";   ','.$ctr.')" />'.$ctr."</label></td>\n";
     $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');      $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
     $ctr++;      $ctr++;
  }   }
  $result.='</tr></table>';   $radio.='</tr></table>';
  $result.= '</td><td><b> or </b><input type="text" name="TEXTVAL_'.   my $line = '<input type="text" name="TEXTVAL_'.
     $partid.'" size="4" '.'onChange="javascript:writePoint(\''.      $partid.'" size="4" '.'onchange="javascript:writePoint(\''.
  $partid.'\','.$weight{$partid}.',\'textval\')" /> /'.   $partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
     $weight{$partid}.' (problem weight)</td>'."\n";      $weight{$partid}.' '.&mt('(problem weight)').'</td>'."\n";
  $result.= '</td><td><select name="SELVAL_'.$partid.'"'.          $line.= '<td><b>'.&mt('Grade Status').':</b>'.
     'onChange="javascript:writeRadText(\''.$partid.'\','.              '<select name="SELVAL_'.$partid.'" '.
  $weight{$partid}.')"> '.              'onchange="javascript:writeRadText(\''.$partid.'\','.
                   $weight{$partid}.')"> '.
     '<option selected="selected"> </option>'.      '<option selected="selected"> </option>'.
     '<option>excused</option>'.      '<option value="excused">'.&mt('excused').'</option>'.
     '<option>reset status</option></select></td>'.      '<option value="reset status">'.&mt('reset status').'</option>'.
             '<td><label><input type="checkbox" name="FORCE_'.$partid.'" /> Override "Correct"</label></td></tr>'."\n";      '</select></td>'.
               '<td><label><input type="checkbox" name="FORCE_'.$partid.'" />'.&mt('Override "Correct"').'</label>';
    $line.='<input type="hidden" name="partid_'.
       $ctsparts.'" value="'.$partid.'" />'."\n";
    $line.='<input type="hidden" name="weight_'.
       $partid.'" value="'.$weight{$partid}.'" />'."\n";
   
    $result.=
       &Apache::loncommon::start_data_table_row()."\n".
       '<td><b>'.&mt('Part:').'</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points:').'</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'.
       &Apache::loncommon::end_data_table_row()."\n";
  $ctsparts++;   $ctsparts++;
     }      }
     $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n".      $result.=&Apache::loncommon::end_data_table()."\n".
  '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';   '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
     $result.='<input type="button" value="Revert to Default" '.      $result.='<input type="button" value="'.&mt('Revert to Default').'" '.
  'onClick="javascript:resetEntry('.$ctsparts.');" target="_self" />';   'onclick="javascript:resetEntry('.$ctsparts.');" />';
   
     #table listing all the students in a section/class      #table listing all the students in a section/class
     #header of table      #header of table
     $result.= '<h3>Assign Grade to Specific Students in '.$sectionClass;      if ($env{'form.submitonly'} eq 'all') {
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".          $result.= '<h3>'.$specific_header.'</h3>';
  '<table border=0><tr bgcolor="#deffff"><td>&nbsp;<b>No.</b>&nbsp;</td>'.      } else {
  '<td>'.&nameUserString('header')."</td>\n";          my $text;
     my (@parts) = sort(&getpartlist($symb));          if ($is_tool) {
               $text = &mt('(transaction status: "[_1]")',$submission_status);
           } else {
               $text = &mt('(submission status: "[_1]")',$submission_status);
           }
           $result.= '<h3>'.$specific_header.'&nbsp;'.$text.'</h3>';
       }
       $result.= &Apache::loncommon::start_data_table().
         &Apache::loncommon::start_data_table_header_row().
         '<th>'.&mt('No.').'</th>'.
         '<th>'.&nameUserString('header')."</th>\n";
       my $partserror;
       my (@parts) = sort(&getpartlist($symb,\$partserror));
       if ($partserror) {
           return &navmap_errormsg();
       }
     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);      my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
     my @partids = ();      my @partids = ();
     foreach my $part (@parts) {      foreach my $part (@parts) {
  my $display=&Apache::lonnet::metadata($url,$part.'.display');   my $display=&Apache::lonnet::metadata($url,$part.'.display',$toolsymb);
  $display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower          my $narrowtext = &mt('Tries');
  if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }   $display =~ s|^Number of Attempts|$narrowtext <br />|; # makes the column narrower
    if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name',$toolsymb); }
  my ($partid) = &split_part_type($part);   my ($partid) = &split_part_type($part);
         push(@partids, $partid);          push(@partids,$partid);
   #
   # FIXME: Looks like $display looks at English text
   #
  my $display_part=&get_display_part($partid,$symb);   my $display_part=&get_display_part($partid,$symb);
  if ($display =~ /^Partial Credit Factor/) {   if ($display =~ /^Partial Credit Factor/) {
     $result.='<td><b>Score Part:</b> '.$display_part.      $result.='<th>'.
  ' <br /><b>(weight = '.$weight{$partid}.')</b></td>'."\n";   &mt('Score Part: [_1][_2](weight = [_3])',
       $display_part,'<br />',$weight{$partid}).'</th>'."\n";
     next;      next;
       
  } else {   } else {
     $display =~s/\[Part: \Q$partid\E\]/Part:<\/b> $display_part/;      if ($display =~ /Problem Status/) {
    my $grade_status_mt = &mt('Grade Status');
    $display =~ s{Problem Status}{$grade_status_mt<br />};
       }
       my $part_mt = &mt('Part:');
       $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part};
  }   }
  $display =~ s|Problem Status|Grade Status<br />|;  
  $result.='<td><b>'.$display.'</td>'."\n";   $result.='<th>'.$display.'</th>'."\n";
     }      }
     $result.='</tr>';      $result.=&Apache::loncommon::end_data_table_header_row();
   
     my %last_resets =       my %last_resets = 
  &get_last_resets($symb,$env{'request.course.id'},\@partids);   &get_last_resets($symb,$env{'request.course.id'},\@partids);
   
     #get info for each student      #get info for each student
     #list all the students - with points and grade status      #list all the students - with points and grade status
     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');      my (undef,undef,$fullname) = &getclasslist(\@sections,'1',\@groups);
     my $ctr = 0;      my $ctr = 0;
     foreach (sort       foreach (sort 
      {       {
Line 3244  sub viewgrades { Line 3997  sub viewgrades {
  }   }
  return $a cmp $b;   return $a cmp $b;
      } (keys(%$fullname))) {       } (keys(%$fullname))) {
  $ctr++;  
  $result.=&viewstudentgrade($symb,$env{'request.course.id'},   $result.=&viewstudentgrade($symb,$env{'request.course.id'},
    $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);     $_,$$fullname{$_},\@parts,\%weight,\$ctr,\%last_resets,$is_tool);
     }      }
     $result.='</table></td></tr></table>';      $result.=&Apache::loncommon::end_data_table();
     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";      $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
     $result.='<input type="button" value="Save" '.      $result.='<input type="button" value="'.&mt('Save').'" '.
  'onClick="javascript:submit();" target="_self" /></form>'."\n";   'onclick="javascript:submit();" target="_self" /></form>'."\n";
     if (scalar(%$fullname) eq 0) {      if ($ctr == 0) {
  my $colspan=3+scalar(@parts);  
  my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));  
         my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));          my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
  $result='<span class="LC_warning">'.          $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>'.
     &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade',                  '<span class="LC_warning">';
         $section_display, $stu_status).          if ($env{'form.submitonly'} eq 'all') {
     '</span>';              if (grep(/^all$/,@sections)) {
                   if (grep(/^all$/,@groups)) {
                       $result .= &mt('There are no students with enrollment status [_1] to modify or grade.',
                                      $stu_status);
                   } elsif (grep(/^none$/,@groups)) {
                       $result .= &mt('There are no students with no group assigned and with enrollment status [_1] to modify or grade.',
                                      $stu_status); 
                   } else {
                       $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] to modify or grade.',
                                      $group_display,$stu_status);
                   }
               } elsif (grep(/^none$/,@sections)) {
                   if (grep(/^all$/,@groups)) {
                       $result .= &mt('There are no students in no section with enrollment status [_1] to modify or grade.',
                                      $stu_status);
                   } elsif (grep(/^none$/,@groups)) {
                       $result .= &mt('There are no students in no section and no group with enrollment status [_1] to modify or grade.',
                                      $stu_status);
                   } else {
                       $result .= &mt('There are no students in no section in group(s) [_1] with enrollment status [_2] to modify or grade.',
                                      $group_display,$stu_status);
                   }
               } else {
                   if (grep(/^all$/,@groups)) {
                       $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
                                      $section_display,$stu_status);
                   } elsif (grep(/^none$/,@groups)) {
                       $result .= &mt('There are no students in section(s) [_1] and no group with enrollment status [_2] to modify or grade.',
                                      $section_display,$stu_status);
                   } else {
                       $result .= &mt('There are no students in section(s) [_1] and group(s) [_2] with enrollment status [_3] to modify or grade.',
                                      $section_display,$group_display,$stu_status);
                   }
               }
           } else {
               if (grep(/^all$/,@sections)) {
                   if (grep(/^all$/,@groups)) {
                       $result .= &mt('There are no students with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                                      $stu_status,$submission_status);
                   } elsif (grep(/^none$/,@groups)) {
                       $result .= &mt('There are no students with no group assigned with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                                      $stu_status,$submission_status);
                   } else {
                       $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
                                      $group_display,$stu_status,$submission_status);
                   }
               } elsif (grep(/^none$/,@sections)) {
                   if (grep(/^all$/,@groups)) {
                       $result .= &mt('There are no students in no section with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                                      $stu_status,$submission_status);
                   } elsif (grep(/^none$/,@groups)) {
                       $result .= &mt('There are no students in no section and no group with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                                      $stu_status,$submission_status);
                   } else {
                       $result .= &mt('There are no students in no section in group(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
                                      $group_display,$stu_status,$submission_status);
                   }
               } else {
                   if (grep(/^all$/,@groups)) {
               $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
                              $section_display,$stu_status,$submission_status);
                   } elsif (grep(/^none$/,@groups)) {
                       $result .= &mt('There are no students in section(s) [_1] and no group with enrollment status [_2] and submission status "[_3]" to modify or grade.',
                                      $section_display,$stu_status,$submission_status);
                   } else {
                       $result .= &mt('There are no students in section(s) [_1] and group(s) [_2] with enrollment status [_3] and submission status "[_4]" to modify or grade.',
                                      $section_display,$group_display,$stu_status,$submission_status);
                   }
               }
           }
    $result .= '</span><br />';
     }      }
     $result.=&show_grading_menu_form($symb);  
     return $result;      return $result;
 }  }
   
 #--- call by previous routine to display each student  #--- call by previous routine to display each student who satisfies submission filter. 
 sub viewstudentgrade {  sub viewstudentgrade {
     my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;      my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets,$is_tool) = @_;
     my ($uname,$udom) = split(/:/,$student);      my ($uname,$udom) = split(/:/,$student);
     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);      my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
     my %aggregates = ();       my $submitonly = $env{'form.submitonly'};
     my $result='<tr bgcolor="#ffffdd"><td align="right">'.      unless (($submitonly eq 'all') || ($submitonly eq 'queued')) {
  '<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.          my %partstatus = ();
  "\n".$ctr.'&nbsp;</td><td>&nbsp;'.          if (ref($parts) eq 'ARRAY') {
               foreach my $apart (@{$parts}) {
                   my ($part,$type) = &split_part_type($apart);
                   my ($status,undef) = split(/_/,$record{"resource.$part.solved"},2);
                   $status = 'nothing' if ($status eq '');
                   $partstatus{$part}      = $status;
                   my $subkey = "resource.$part.submitted_by";
                   $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
               }
               my $submitted = 0;
               my $graded = 0;
               my $incorrect = 0;
               foreach my $key (keys(%partstatus)) {
                   $submitted = 1 if ($partstatus{$key} ne 'nothing');
                   $graded = 1 if ($partstatus{$key} =~ /^ungraded/);
                   $incorrect = 1 if ($partstatus{$key} =~ /^incorrect/);
   
                   my $partid = (split(/\./,$key))[1];
                   if ($partstatus{'resource.'.$partid.'.'.$key.'.submitted_by'} ne '') {
                       $submitted = 0;
                   }
               }
               return if (!$submitted && ($submitonly eq 'yes' ||
                                          $submitonly eq 'incorrect' ||
                                          $submitonly eq 'graded'));
               return if (!$graded && ($submitonly eq 'graded'));
               return if (!$incorrect && $submitonly eq 'incorrect');
           }
       }
       if ($submitonly eq 'queued') {
           my ($cdom,$cnum) = split(/_/,$courseid);
           my %queue_status =
               &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
                                                       $udom,$uname);
           return if (!defined($queue_status{'gradingqueue'}));
       }
       $$ctr++;
       my %aggregates = ();
       my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'.
    '<input type="hidden" name="ctr'.($$ctr-1).'" value="'.$student.'" />'.
    "\n".$$ctr.'&nbsp;</td><td>&nbsp;'.
  '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.   '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
  '\');" target="_self">'.$fullname.'</a> '.   '\');" target="_self">'.$fullname.'</a> '.
  '<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";   '<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
Line 3285  sub viewstudentgrade { Line 4144  sub viewstudentgrade {
         my ($aggtries,$totaltries);          my ($aggtries,$totaltries);
         unless (exists($aggregates{$part})) {          unless (exists($aggregates{$part})) {
     $totaltries = $record{'resource.'.$part.'.tries'};      $totaltries = $record{'resource.'.$part.'.tries'};
   
     $aggtries = $totaltries;      $aggtries = $totaltries;
             if ($$last_resets{$part}) {                if ($$last_resets{$part}) {  
                 $aggtries = &get_num_tries(\%record,$$last_resets{$part},                  $aggtries = &get_num_tries(\%record,$$last_resets{$part},
Line 3303  sub viewstudentgrade { Line 4161  sub viewstudentgrade {
  'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";   'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
     $result.='<input type="text" name="'.      $result.='<input type="text" name="'.
  'GD_'.$student.'_'.$part.'_awarded" '.   'GD_'.$student.'_'.$part.'_awarded" '.
  'onChange="javascript:changeSelect(\''.$part.'\',\''.$student.                  'onchange="javascript:changeSelect(\''.$part.'\',\''.$student.
  '\')" value="'.$pts.'" size="4" /></td>'."\n";   '\')" value="'.$pts.'" size="4" /></td>'."\n";
  } elsif ($type eq 'solved') {   } elsif ($type eq 'solved') {
     my ($status,$foo)=split(/_/,$score,2);      my ($status,$foo)=split(/_/,$score,2);
Line 3312  sub viewstudentgrade { Line 4170  sub viewstudentgrade {
  $part.'_solved_s" value="'.$status.'" />'."\n";   $part.'_solved_s" value="'.$status.'" />'."\n";
     $result.='&nbsp;<select name="'.      $result.='&nbsp;<select name="'.
  'GD_'.$student.'_'.$part.'_solved" '.   'GD_'.$student.'_'.$part.'_solved" '.
  'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";                  'onchange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
     $result.= (($status eq 'excused') ? '<option> </option><option selected="selected">excused</option>'       $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
  : '<option selected="selected"> </option><option>excused</option>')."\n";   : '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
     $result.='<option>reset status</option>';      $result.='<option value="reset status">'.&mt('reset status').'</option>';
     $result.="</select>&nbsp;</td>\n";      $result.="</select>&nbsp;</td>\n";
  } else {   } else {
     $result.='<input type="hidden" name="'.      $result.='<input type="hidden" name="'.
Line 3326  sub viewstudentgrade { Line 4184  sub viewstudentgrade {
  'value="'.$score.'" size="4" /></td>'."\n";   'value="'.$score.'" size="4" /></td>'."\n";
  }   }
     }      }
     $result.='</tr>';      $result.=&Apache::loncommon::end_data_table_row();
     return $result;      return $result;
 }  }
   
 #--- change scores for all the students in a section/class  #--- change scores for all the students in a section/class
 #    record does not get update if unchanged  #    record does not get update if unchanged
 sub editgrades {  sub editgrades {
     my ($request) = @_;      my ($request,$symb) = @_;
       my $toolsymb;
       if ($symb =~ /ext\.tool$/) {
           $toolsymb = $symb;
       }
   
     my $symb=&get_symb($request);  
     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));      my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     my $title='<h3><span class="LC_info">'.&mt('Current Grade Status').'</span></h3>';      my $title='<h2>'.&mt('Current Grade Status').'</h2>';
     $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4><br />'."\n";  
     $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";      $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
   
     my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";      my $result= &Apache::loncommon::start_data_table().
     $result.= '<table border="0"><tr bgcolor="#deffff">'.   &Apache::loncommon::start_data_table_header_row().
  '<td rowspan=2 valign="center">&nbsp;<b>No.</b>&nbsp;</td>'.   '<th rowspan="2" valign="middle">'.&mt('No.').'</th>'.
  '<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n";   '<th rowspan="2" valign="middle">'.&nameUserString('header')."</th>\n";
   
     my %scoreptr = (      my %scoreptr = (
     'correct'  =>'correct_by_override',      'correct'  =>'correct_by_override',
     'incorrect'=>'incorrect_by_override',      'incorrect'=>'incorrect_by_override',
     'excused'  =>'excused',      'excused'  =>'excused',
     'ungraded' =>'ungraded_attempted',      'ungraded' =>'ungraded_attempted',
                       'credited' =>'credit_attempted',
     'nothing'  => '',      'nothing'  => '',
     );      );
     my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');      my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
Line 3360  sub editgrades { Line 4220  sub editgrades {
     my %columns = ();      my %columns = ();
     my ($i,$ctr,$count,$rec_update) = (0,0,0,0);      my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
   
     my (@parts) = sort(&getpartlist($symb));      my $partserror;
       my (@parts) = sort(&getpartlist($symb,\$partserror));
       if ($partserror) {
           return &navmap_errormsg();
       }
     my $header;      my $header;
     while ($ctr < $env{'form.totalparts'}) {      while ($ctr < $env{'form.totalparts'}) {
  my $partid = $env{'form.partid_'.$ctr};   my $partid = $env{'form.partid_'.$ctr};
  push @partid,$partid;   push(@partid,$partid);
  $weight{$partid} = $env{'form.weight_'.$partid};   $weight{$partid} = $env{'form.weight_'.$partid};
  $ctr++;   $ctr++;
     }      }
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
       my $totcolspan = 0;
     foreach my $partid (@partid) {      foreach my $partid (@partid) {
  $header .= '<td align="center">&nbsp;<b>Old Score</b>&nbsp;</td>'.   $header .= '<th align="center">'.&mt('Old Score').'</th>'.
     '<td align="center">&nbsp;<b>New Score</b>&nbsp;</td>';      '<th align="center">'.&mt('New Score').'</th>';
  $columns{$partid}=2;   $columns{$partid}=2;
  foreach my $stores (@parts) {   foreach my $stores (@parts) {
     my ($part,$type) = &split_part_type($stores);      my ($part,$type) = &split_part_type($stores);
     if ($part !~ m/^\Q$partid\E/) { next;}      if ($part !~ m/^\Q$partid\E/) { next;}
     if ($type eq 'awarded' || $type eq 'solved') { next; }      if ($type eq 'awarded' || $type eq 'solved') { next; }
     my $display=&Apache::lonnet::metadata($url,$stores.'.display');      my $display=&Apache::lonnet::metadata($url,$stores.'.display',$toolsymb);
     $display =~ s/\[Part: (\w)+\]//;      $display =~ s/\[Part: \Q$part\E\]//;
     $display =~ s/Number of Attempts/Tries/;              my $narrowtext = &mt('Tries');
     $header .= '<td align="center">&nbsp;<b>Old '.$display.'</b>&nbsp;</td>'.      $display =~ s/Number of Attempts/$narrowtext/;
  '<td align="center">&nbsp;<b>New '.$display.'</b>&nbsp;</td>';      $header .= '<th align="center">'.&mt('Old').' '.$display.'</th>'.
    '<th align="center">'.&mt('New').' '.$display.'</th>';
     $columns{$partid}+=2;      $columns{$partid}+=2;
  }   }
           $totcolspan += $columns{$partid};
     }      }
     foreach my $partid (@partid) {      foreach my $partid (@partid) {
  my $display_part=&get_display_part($partid,$symb);   my $display_part=&get_display_part($partid,$symb);
  $result .= '<td colspan="'.$columns{$partid}.   $result .= '<th colspan="'.$columns{$partid}.'" align="center">'.
     '" align="center"><b>Part:</b> '.$display_part.      &mt('Part: [_1] (Weight = [_2])',$display_part,$weight{$partid}).
     ' (Weight = '.$weight{$partid}.')</td>';      '</th>';
   
     }      }
     $result .= '</tr><tr bgcolor="#deffff">';      $result .= &Apache::loncommon::end_data_table_header_row().
     $result .= $header;   &Apache::loncommon::start_data_table_header_row().
     $result .= '</tr>'."\n";   $header.
     my $noupdate;   &Apache::loncommon::end_data_table_header_row();
       my @noupdate;
     my ($updateCtr,$noupdateCtr) = (1,1);      my ($updateCtr,$noupdateCtr) = (1,1);
     for ($i=0; $i<$env{'form.total'}; $i++) {      for ($i=0; $i<$env{'form.total'}; $i++) {
  my $line;  
  my $user = $env{'form.ctr'.$i};   my $user = $env{'form.ctr'.$i};
  my ($uname,$udom)=split(/:/,$user);   my ($uname,$udom)=split(/:/,$user);
  my %newrecord;   my %newrecord;
  my $updateflag = 0;   my $updateflag = 0;
  $line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';  
  my $usec=$classlist->{"$uname:$udom"}[5];   my $usec=$classlist->{"$uname:$udom"}[5];
  if (!&canmodify($usec)) {   my $canmodify = &canmodify($usec);
     my $numcols=scalar(@partid)*4+2;   my $line = '<td'.($canmodify?'':' colspan="2"').'>'.
     $noupdate.=$line."<td colspan=\"$numcols\"><span class=\"LC_warning\">Not allowed to modify student</span></td></tr>";     &nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
    if (!$canmodify) {
       push(@noupdate,
    $line."<td colspan=\"$totcolspan\"><span class=\"LC_warning\">".
    &mt('Not allowed to modify student')."</span></td>");
     next;      next;
  }   }
         my %aggregate = ();          my %aggregate = ();
Line 3476  sub editgrades { Line 4346  sub editgrades {
     '<td align="center">'.$awarded.'&nbsp;</td>';      '<td align="center">'.$awarded.'&nbsp;</td>';
     }      }
  }   }
  $line.='</tr>'."\n";   $line.="\n";
   
  my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};   my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
  my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};   my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
Line 3509  sub editgrades { Line 4379  sub editgrades {
  }   }
     }      }
   
     $result.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line;      $result.=&Apache::loncommon::start_data_table_row().
    '<td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line.
    &Apache::loncommon::end_data_table_row();
     $updateCtr++;      $updateCtr++;
  } else {   } else {
     $noupdate.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line;      push(@noupdate,
    '<td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line);
     $noupdateCtr++;      $noupdateCtr++;
  }   }
         if ($aggregateflag) {          if ($aggregateflag) {
Line 3520  sub editgrades { Line 4393  sub editgrades {
   $cdom,$cnum);    $cdom,$cnum);
         }          }
     }      }
     if ($noupdate) {      if (@noupdate) {
 # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;          my $numcols=$totcolspan+2;
  my $numcols=scalar(@partid)*4+2;   $result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
  $result .= '<tr bgcolor="#ffffff"><td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td></tr><tr bgcolor="#ffffde">'.$noupdate;      '<td align="center" colspan="'.$numcols.'">'.
     }      &mt('No Changes Occurred For the Students Below').
     $result .= '</table></td></tr></table>'."\n".      '</td>'.
  &show_grading_menu_form ($symb);      &Apache::loncommon::end_data_table_row();
     my $msg = '<br /><b>Number of records updated = '.$rec_update.   foreach my $line (@noupdate) {
  ' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'.      $result.=
  '<b>Total number of students = '.$env{'form.total'}.'</b><br />';   &Apache::loncommon::start_data_table_row().
    $line.
    &Apache::loncommon::end_data_table_row();
    }
       }
       $result .= &Apache::loncommon::end_data_table();
       my $msg = '<p><b>'.
    &mt('Number of records updated = [_1] for [quant,_2,student].',
       $rec_update,$count).'</b><br />'.
    '<b>'.&mt('Total number of students = [_1]',$env{'form.total'}).
    '</b></p>';
     return $title.$msg.$result;      return $title.$msg.$result;
 }  }
   
Line 3552  sub split_part_type { Line 4435  sub split_part_type {
 #  #
 #--- Javascript to handle csv upload  #--- Javascript to handle csv upload
 sub csvupload_javascript_reverse_associate {  sub csvupload_javascript_reverse_associate {
     my $error1=&mt('You need to specify the username or ID');      my $error1=&mt('You need to specify the username, the student/employee ID, or the clicker ID');
     my $error2=&mt('You need to specify at least one grading field');      my $error2=&mt('You need to specify at least one grading field');
     &js_escape(\$error1);
     &js_escape(\$error2);
   return(<<ENDPICK);    return(<<ENDPICK);
   function verify(vf) {    function verify(vf) {
     var foundsomething=0;      var foundsomething=0;
     var founduname=0;      var founduname=0;
     var foundID=0;      var foundID=0;
       var foundclicker=0;
     for (i=0;i<=vf.nfields.value;i++) {      for (i=0;i<=vf.nfields.value;i++) {
       tw=eval('vf.f'+i+'.selectedIndex');        tw=eval('vf.f'+i+'.selectedIndex');
       if (i==0 && tw!=0) { foundID=1; }        if (i==0 && tw!=0) { foundID=1; }
       if (i==1 && tw!=0) { founduname=1; }        if (i==1 && tw!=0) { founduname=1; }
       if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }        if (i==2 && tw!=0) { foundclicker=1; }
         if (i!=0 && i!=1 && i!=2 && i!=3 && tw!=0) { foundsomething=1; }
     }      }
     if (founduname==0 && foundID==0) {      if (founduname==0 && foundID==0 && foundclicker==0) {
  alert('$error1');   alert('$error1');
  return;   return;
     }      }
Line 3592  ENDPICK Line 4479  ENDPICK
 }  }
   
 sub csvupload_javascript_forward_associate {  sub csvupload_javascript_forward_associate {
     my $error1=&mt('You need to specify the username or ID');      my $error1=&mt('You need to specify the username, the student/employee ID, or the clicker ID');
     my $error2=&mt('You need to specify at least one grading field');      my $error2=&mt('You need to specify at least one grading field');
     &js_escape(\$error1);
     &js_escape(\$error2);
   return(<<ENDPICK);    return(<<ENDPICK);
   function verify(vf) {    function verify(vf) {
     var foundsomething=0;      var foundsomething=0;
     var founduname=0;      var founduname=0;
     var foundID=0;      var foundID=0;
       var foundclicker=0;
     for (i=0;i<=vf.nfields.value;i++) {      for (i=0;i<=vf.nfields.value;i++) {
       tw=eval('vf.f'+i+'.selectedIndex');        tw=eval('vf.f'+i+'.selectedIndex');
       if (tw==1) { foundID=1; }        if (tw==1) { foundID=1; }
       if (tw==2) { founduname=1; }        if (tw==2) { founduname=1; }
       if (tw>3) { foundsomething=1; }        if (tw==3) { foundclicker=1; }
         if (tw>4) { foundsomething=1; }
     }      }
     if (founduname==0 && foundID==0) {      if (founduname==0 && foundID==0 && Æ’oundclicker==0) {
  alert('$error1');   alert('$error1');
  return;   return;
     }      }
Line 3637  sub csvuploadmap_header { Line 4528  sub csvuploadmap_header {
  $javascript=&csvupload_javascript_forward_associate();   $javascript=&csvupload_javascript_forward_associate();
     }      }
   
     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});  
     my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');  
     my $ignore=&mt('Ignore First Line');  
     $symb = &Apache::lonenc::check_encrypt($symb);      $symb = &Apache::lonenc::check_encrypt($symb);
       $request->print('<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">'.
                       &mt('Total number of records found in file: [_1]',$distotal).'<hr />'.
                       &mt('Associate entries from the uploaded file with as many fields as you can.'));
       my $reverse=&mt("Reverse Association");
     $request->print(<<ENDPICK);      $request->print(<<ENDPICK);
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">  <br />
 <h3><span class="LC_info">Uploading Class Grades</span></h3>  <input type="button" value="$reverse" onclick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
 $result  
 <hr />  
 <h3>Identify fields</h3>  
 Total number of records found in file: $distotal <hr />  
 Enter as many fields as you can. The system will inform you and bring you back  
 to this page if the data selected is insufficient to run your class.<hr />  
 <input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />  
 <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>  
 <input type="hidden" name="associate"  value="" />  <input type="hidden" name="associate"  value="" />
 <input type="hidden" name="phase"      value="three" />  <input type="hidden" name="phase"      value="three" />
 <input type="hidden" name="datatoken"  value="$datatoken" />  <input type="hidden" name="datatoken"  value="$datatoken" />
Line 3660  to this page if the data selected is ins Line 4544  to this page if the data selected is ins
 <input type="hidden" name="upfile_associate"   <input type="hidden" name="upfile_associate" 
                                        value="$env{'form.upfile_associate'}" />                                         value="$env{'form.upfile_associate'}" />
 <input type="hidden" name="symb"       value="$symb" />  <input type="hidden" name="symb"       value="$symb" />
 <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />  
 <input type="hidden" name="probTitle"  value="$env{'form.probTitle'}" />  
 <input type="hidden" name="command"    value="csvuploadoptions" />  <input type="hidden" name="command"    value="csvuploadoptions" />
 <hr />  <hr />
 <script type="text/javascript" language="Javascript">  
 $javascript  
 </script>  
 ENDPICK  ENDPICK
       $request->print(&Apache::lonhtmlcommon::scripttag($javascript));
     return '';      return '';
   
 }  }
   
 sub csvupload_fields {  sub csvupload_fields {
     my ($symb) = @_;      my ($symb,$errorref) = @_;
     my (@parts) = &getpartlist($symb);      my $toolsymb;
     my @fields=(['ID','Student ID'],      if ($symb =~ /ext\.tool$/) {
           $toolsymb = $symb;
       }
       my (@parts) = &getpartlist($symb,$errorref);
       if (ref($errorref)) {
           if ($$errorref) {
               return;
           }
       }
   
       my @fields=(['ID','Student/Employee ID'],
  ['username','Student Username'],   ['username','Student Username'],
    ['clicker','Clicker ID'],
  ['domain','Student Domain']);   ['domain','Student Domain']);
     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     foreach my $part (sort(@parts)) {      foreach my $part (sort(@parts)) {
  my @datum;   my @datum;
  my $display=&Apache::lonnet::metadata($url,$part.'.display');   my $display=&Apache::lonnet::metadata($url,$part.'.display',$toolsymb);
  my $name=$part;   my $name=$part;
  if  (!$display) { $display = $name; }   if (!$display) { $display = $name; }
  @datum=($name,$display);   @datum=($name,$display);
  if ($name=~/^stores_(.*)_awarded/) {   if ($name=~/^stores_(.*)_awarded/) {
     push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);      push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
Line 3695  sub csvupload_fields { Line 4586  sub csvupload_fields {
   
 sub csvuploadmap_footer {  sub csvuploadmap_footer {
     my ($request,$i,$keyfields) =@_;      my ($request,$i,$keyfields) =@_;
       my $buttontext = &mt('Assign Grades');
     $request->print(<<ENDPICK);      $request->print(<<ENDPICK);
 </table>  </table>
 <input type="hidden" name="nfields" value="$i" />  <input type="hidden" name="nfields" value="$i" />
 <input type="hidden" name="keyfields" value="$keyfields" />  <input type="hidden" name="keyfields" value="$keyfields" />
 <input type="button" onClick="javascript:verify(this.form)" value="Assign Grades" /><br />  <input type="button" onclick="javascript:verify(this.form)" value="$buttontext" /><br />
 </form>  </form>
 ENDPICK  ENDPICK
 }  }
   
 sub checkforfile_js {  sub checkforfile_js {
     my $result =<<CSVFORMJS;      my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
 <script type="text/javascript" language="javascript">      &js_escape(\$alertmsg);
       my $result = &Apache::lonhtmlcommon::scripttag(<<CSVFORMJS);
     function checkUpload(formname) {      function checkUpload(formname) {
  if (formname.upfile.value == "") {   if (formname.upfile.value == "") {
     alert("Please use the browse button to select a file from your local directory.");      alert("$alertmsg");
     return false;      return false;
  }   }
  formname.submit();   formname.submit();
     }      }
     </script>  
 CSVFORMJS  CSVFORMJS
     return $result;      return $result;
 }  }
   
 sub upcsvScores_form {  sub upcsvScores_form {
     my ($request) = shift;      my ($request,$symb) = @_;
     my ($symb)=&get_symb($request);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $result=&checkforfile_js();      my $result=&checkforfile_js();
     $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);      $result.=&Apache::loncommon::start_data_table().
     my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});               &Apache::loncommon::start_data_table_header_row().
     $result.=$table;               '<th>'.&mt('Specify a file containing the class scores for current resource.').'</th>'.
     $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";               &Apache::loncommon::end_data_table_header_row().
     $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";               &Apache::loncommon::start_data_table_row().'<td>';
     $result.='&nbsp;<b>'.&mt('Specify a file containing the class scores for current resource').  
  '.</b></td></tr>'."\n";  
     $result.='<tr bgcolor=#ffffe6><td>'."\n";  
     my $upload=&mt("Upload Scores");      my $upload=&mt("Upload Scores");
     my $upfile_select=&Apache::loncommon::upfile_select_html();      my $upfile_select=&Apache::loncommon::upfile_select_html();
     my $ignore=&mt('Ignore First Line');      my $ignore=&mt('Ignore First Line');
Line 3740  sub upcsvScores_form { Line 4628  sub upcsvScores_form {
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <input type="hidden" name="symb" value="$symb" />  <input type="hidden" name="symb" value="$symb" />
 <input type="hidden" name="command" value="csvuploadmap" />  <input type="hidden" name="command" value="csvuploadmap" />
 <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />  
 <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />  
 $upfile_select  $upfile_select
 <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />  <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
 <label><input type="checkbox" name="noFirstLine" />$ignore</label>  
 </form>  </form>
 ENDUPFORM  ENDUPFORM
     $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",      $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
                            &mt("How do I create a CSV file from a spreadsheet"))                             &mt("How do I create a CSV file from a spreadsheet")).
     .'</td></tr></table>'."\n";               '</td>'.
     $result.='</td></tr></table><br /><br />'."\n";              &Apache::loncommon::end_data_table_row().
     $result.=&show_grading_menu_form($symb);              &Apache::loncommon::end_data_table();
     return $result;      return $result;
 }  }
   
   
 sub csvuploadmap {  sub csvuploadmap {
     my ($request)= @_;      my ($request,$symb)= @_;
     my ($symb)=&get_symb($request);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
   
     my $datatoken;      my $datatoken;
     if (!$env{'form.datatoken'}) {      if (!$env{'form.datatoken'}) {
  $datatoken=&Apache::loncommon::upfile_store($request);   $datatoken=&Apache::loncommon::upfile_store($request);
     } else {      } else {
  $datatoken=$env{'form.datatoken'};   $datatoken=&Apache::loncommon::valid_datatoken($env{'form.datatoken'});
  &Apache::loncommon::load_tmp_file($request);          if ($datatoken ne '') {
       &Apache::loncommon::load_tmp_file($request,$datatoken);
           }
     }      }
     my @records=&Apache::loncommon::upfile_record_sep();      my @records=&Apache::loncommon::upfile_record_sep();
     if ($env{'form.noFirstLine'}) { shift(@records); }  
     &csvuploadmap_header($request,$symb,$datatoken,$#records+1);      &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
     my ($i,$keyfields);      my ($i,$keyfields);
     if (@records) {      if (@records) {
  my @fields=&csvupload_fields($symb);          my $fieldserror;
    my @fields=&csvupload_fields($symb,\$fieldserror);
           if ($fieldserror) {
               $request->print(&navmap_errormsg());
               return;
           }
  if ($env{'form.upfile_associate'} eq 'reverse') {   if ($env{'form.upfile_associate'} eq 'reverse') {
     &Apache::loncommon::csv_print_samples($request,\@records);      &Apache::loncommon::csv_print_samples($request,\@records);
     $i=&Apache::loncommon::csv_print_select_table($request,\@records,      $i=&Apache::loncommon::csv_print_select_table($request,\@records,
Line 3795  sub csvuploadmap { Line 4684  sub csvuploadmap {
  }   }
     }      }
     &csvuploadmap_footer($request,$i,$keyfields);      &csvuploadmap_footer($request,$i,$keyfields);
     $request->print(&show_grading_menu_form($symb));  
   
     return '';      return '';
 }  }
   
 sub csvuploadoptions {  sub csvuploadoptions {
     my ($request)= @_;      my ($request,$symb)= @_;
     my ($symb)=&get_symb($request);      my $overwrite=&mt('Overwrite any existing score');
     my $checked=(($env{'form.noFirstLine'})?'1':'0');  
     my $ignore=&mt('Ignore First Line');  
     $request->print(<<ENDPICK);      $request->print(<<ENDPICK);
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <h3><span class="LC_info">Uploading Class Grade Options</span></h3>  
 <input type="hidden" name="command"    value="csvuploadassign" />  <input type="hidden" name="command"    value="csvuploadassign" />
 <!--  
 <p>  
 <label>  
    <input type="checkbox" name="show_full_results" />  
    Show a table of all changes  
 </label>  
 </p>  
 -->  
 <p>  <p>
 <label>  <label>
    <input type="checkbox" name="overwite_scores" checked="checked" />     <input type="checkbox" name="overwite_scores" checked="checked" />
    Overwrite any existing score     $overwrite
 </label>  </label>
 </p>  </p>
 ENDPICK  ENDPICK
     my %fields=&get_fields();      my %fields=&get_fields();
     if (!defined($fields{'domain'})) {      if (!defined($fields{'domain'})) {
  my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');   my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
  $request->print("\n<p> Users are in domain: ".$domform."</p>\n");   $request->print("\n<p>".&mt('Users are in domain: [_1]',$domform)."</p>\n");
     }      }
     foreach my $key (sort(keys(%env))) {      foreach my $key (sort(keys(%env))) {
  if ($key !~ /^form\.(.*)$/) { next; }   if ($key !~ /^form\.(.*)$/) { next; }
Line 3838  ENDPICK Line 4715  ENDPICK
     }      }
     # FIXME do a check for any duplicated user ids...      # FIXME do a check for any duplicated user ids...
     # FIXME do a check for any invalid user ids?...      # FIXME do a check for any invalid user ids?...
     $request->print('<input type="submit" value="Assign Grades" /><br />      $request->print('<input type="submit" value="'.&mt('Assign Grades').'" /><br />
 <hr /></form>'."\n");  <hr /></form>'."\n");
     $request->print(&show_grading_menu_form($symb));  
     return '';      return '';
 }  }
   
Line 3862  sub get_fields { Line 4738  sub get_fields {
 }  }
   
 sub csvuploadassign {  sub csvuploadassign {
     my ($request)= @_;      my ($request,$symb) = @_;
     my ($symb)=&get_symb($request);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $error_msg = '';      my $error_msg = '';
     &Apache::loncommon::load_tmp_file($request);      my $datatoken = &Apache::loncommon::valid_datatoken($env{'form.datatoken'});
       if ($datatoken ne '') { 
           &Apache::loncommon::load_tmp_file($request,$datatoken);
       }
     my @gradedata = &Apache::loncommon::upfile_record_sep();      my @gradedata = &Apache::loncommon::upfile_record_sep();
     if ($env{'form.noFirstLine'}) { shift(@gradedata); }  
     my %fields=&get_fields();      my %fields=&get_fields();
     $request->print('<h3>Assigning Grades</h3>');  
     my $courseid=$env{'request.course.id'};      my $courseid=$env{'request.course.id'};
     my ($classlist) = &getclasslist('all',0);      my ($classlist) = &getclasslist('all',0);
     my @notallowed;      my @notallowed;
     my @skipped;      my @skipped;
       my @warnings;
     my $countdone=0;      my $countdone=0;
     foreach my $grade (@gradedata) {      foreach my $grade (@gradedata) {
  my %entries=&Apache::loncommon::record_sep($grade);   my %entries=&Apache::loncommon::record_sep($grade);
Line 3890  sub csvuploadassign { Line 4767  sub csvuploadassign {
  if (!$username) {   if (!$username) {
     my $id=$entries{$fields{'ID'}};      my $id=$entries{$fields{'ID'}};
     $id=~s/\s//g;      $id=~s/\s//g;
     my %ids=&Apache::lonnet::idget($domain,$id);              if ($id ne '') {
     $username=$ids{$id};          my %ids=&Apache::lonnet::idget($domain,[$id]);
           $username=$ids{$id};
               } else {
                   if ($entries{$fields{'clicker'}}) {
                       my $clicker = $entries{$fields{'clicker'}};
                       $clicker=~s/\s//g;
                       if ($clicker ne '') {
                           my %clickers = &Apache::lonnet::idget($domain,[$clicker],'clickers');
                           if ($clickers{$clicker} ne '') {  
                               my $match = 0;
                               my @inclass;
                               foreach my $poss (split(/,/,$clickers{$clicker})) {
                                   if (exists($$classlist{"$poss:$domain"})) {
                                       $username = $poss;
                                       push(@inclass,$poss);
                                       $match ++;
                                       
                                   }
                               }
                               if ($match > 1) {
                                   undef($username); 
                                   $request->print('<p class="LC_warning">'.
                                                   &mt('Score not saved for clicker: [_1] (matched multiple usernames: [_2])',
                                                   $clicker,join(', ',@inclass)).'</p>');
                               }
                           }
                       }
                   }
               }
  }   }
  if (!exists($$classlist{"$username:$domain"})) {   if (!exists($$classlist{"$username:$domain"})) {
     my $id=$entries{$fields{'ID'}};      my $id=$entries{$fields{'ID'}};
     $id=~s/\s//g;      $id=~s/\s//g;
     if ($id) {              my $clicker = $entries{$fields{'clicker'}};
               $clicker=~s/\s//g;
               if ($clicker) {
                   push(@skipped,"$clicker:$domain");
       } elsif ($id) {
  push(@skipped,"$id:$domain");   push(@skipped,"$id:$domain");
     } else {      } else {
  push(@skipped,"$username:$domain");   push(@skipped,"$username:$domain");
Line 3923  sub csvuploadassign { Line 4832  sub csvuploadassign {
                     my $pcr=$entries{$fields{$dest}} / $wgt;                      my $pcr=$entries{$fields{$dest}} / $wgt;
                     my $award=($pcr == 0) ? 'incorrect_by_override'                      my $award=($pcr == 0) ? 'incorrect_by_override'
                                           : 'correct_by_override';                                            : 'correct_by_override';
                       if ($pcr>1) {
                          push(@warnings,&mt("[_1]: point value larger than weight","$username:$domain"));
                       }
                     $grades{"resource.$part.awarded"}=$pcr;                      $grades{"resource.$part.awarded"}=$pcr;
                     $grades{"resource.$part.solved"}=$award;                      $grades{"resource.$part.solved"}=$award;
                     $points{$part}=1;                      $points{$part}=1;
Line 3942  sub csvuploadassign { Line 4854  sub csvuploadassign {
  $grades{$store_key}=$entries{$fields{$dest}};   $grades{$store_key}=$entries{$fields{$dest}};
     }      }
  }   }
  if (! %grades) { push(@skipped,"$username:$domain no data to save"); }   if (! %grades) {
  $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";             push(@skipped,&mt("[_1]: no data to save","$username:$domain")); 
  my $result=&Apache::lonnet::cstore(\%grades,$symb,          } else {
      $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
      my $result=&Apache::lonnet::cstore(\%grades,$symb,
    $env{'request.course.id'},     $env{'request.course.id'},
    $domain,$username);     $domain,$username);
  if ($result eq 'ok') {     if ($result eq 'ok') {
     $request->print('.');  # Successfully stored
  } else {        $request->print('.');
     $request->print("<p>  # Remove from grading queue
                               <span class=\"LC_error\">                &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,
                                  Failed to save student $username:$domain.                                               $env{'course.'.$env{'request.course.id'}.'.domain'},
                                  Message when trying to save was ($result)                                               $env{'course.'.$env{'request.course.id'}.'.num'},
                               </span>                                               $domain,$username);
                              </p>" );                $countdone++;
  }             } else {
  $request->rflush();        $request->print("<p><span class=\"LC_error\">".
  $countdone++;                                &mt("Failed to save data for student [_1]. Message when trying to save was: [_2]",
                                     "$username:$domain",$result)."</span></p>");
      }
      $request->rflush();
           }
       }
       $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
       if (@warnings) {
           $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Warnings generated for the following saved scores:'),1).'<br />');
           $request->print(join(', ',@warnings));
     }      }
     $request->print("<br />Saved $countdone students\n");  
     if (@skipped) {      if (@skipped) {
  $request->print('<p><h4><b>Skipped Students</b></h4></p>');   $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('No scores stored for the following username(s):'),1).'<br />');
  foreach my $student (@skipped) { $request->print("$student<br />\n"); }          $request->print(join(', ',@skipped));
     }      }
     if (@notallowed) {      if (@notallowed) {
  $request->print('<p><span class="LC_error">Students Not Allowed to Modify</span></p>');   $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Modification of scores not allowed for the following username(s):'),1).'<br />');
  foreach my $student (@notallowed) { $request->print("$student<br />\n"); }   $request->print(join(', ',@notallowed));
     }      }
     $request->print("<br />\n");      $request->print("<br />\n");
     $request->print(&show_grading_menu_form($symb));  
     return $error_msg;      return $error_msg;
 }  }
 #------------- end of section for handling csv file upload ---------  #------------- end of section for handling csv file upload ---------
Line 3981  sub csvuploadassign { Line 4902  sub csvuploadassign {
 #  #
 #--- Select a page/sequence and a student to grade  #--- Select a page/sequence and a student to grade
 sub pickStudentPage {  sub pickStudentPage {
     my ($request) = shift;      my ($request,$symb) = @_;
   
     $request->print(<<LISTJAVASCRIPT);      my $alertmsg = &mt('Please select the student you wish to grade.');
 <script type="text/javascript" language="javascript">      &js_escape(\$alertmsg);
       $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
   
 function checkPickOne(formname) {  function checkPickOne(formname) {
     if (radioSelection(formname.student) == null) {      if (radioSelection(formname.student) == null) {
  alert("Please select the student you wish to grade.");   alert("$alertmsg");
  return;   return;
     }      }
     ptr = pullDownSelection(formname.selectpage);      ptr = pullDownSelection(formname.selectpage);
Line 3997  function checkPickOne(formname) { Line 4919  function checkPickOne(formname) {
     formname.submit();      formname.submit();
 }  }
   
 </script>  
 LISTJAVASCRIPT  LISTJAVASCRIPT
     &commonJSfunctions($request);      &commonJSfunctions($request);
     my ($symb) = &get_symb($request);  
     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 $result='<h3><span class="LC_info">&nbsp;'.      my $result='<h3><span class="LC_info">&nbsp;'.
  'Manual Grading by Page or Sequence</span></h3>';   &mt('Manual Grading by Page or Sequence').'</span></h3>';
   
     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";      $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
     $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";      my $map_error;
     my ($titles,$symbx) = &getSymbMap();      my ($titles,$symbx) = &getSymbMap($map_error);
       if ($map_error) {
           $request->print(&navmap_errormsg());
           return; 
       }
     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)/);
   
       # Collection of hidden fields
     my $ctr=0;      my $ctr=0;
     foreach (@$titles) {      foreach (@$titles) {
  my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);          my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
  $result.='<option value="'.$ctr.'" '.          $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
     ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').          $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
     '>'.$showtitle.'</option>'."\n";          $ctr++;
  $ctr++;  
     }  
     $result.= '</select>'."<br />\n";  
     $ctr=0;  
     foreach (@$titles) {  
  my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);  
  $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";  
  $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";  
  $ctr++;  
     }      }
     $result.='<input type="hidden" name="page" />'."\n".      $result.='<input type="hidden" name="page" />'."\n".
  '<input type="hidden" name="title" />'."\n";          '<input type="hidden" name="title" />'."\n";
   
     $result.='&nbsp;<b>View Problems Text: </b><label><input type="radio" name="vProb" value="no" checked="checked" /> no </label>'."\n".  
  '<label><input type="radio" name="vProb" value="yes" /> yes </label>'."<br />\n";  
   
     $result.='&nbsp;<b>Submission Details: </b>'.  
  '<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="all" /> all details</label>'."\n";  
       
     $result.=&build_section_inputs();      $result.=&build_section_inputs();
     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     $result.='<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".      $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";  
   
     $result.='&nbsp;<b>'.&mt('Use CODE:').' </b>'.      # Show grading options
  '<input type="text" name="CODE" value="" /><br />'."\n";      $result.=&Apache::lonhtmlcommon::start_pick_box();
       my $select = '<select name="selectpage">'."\n";
       $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $select.='<option value="'.$ctr.'"'.
       ($$symbx{$_} =~ /$curpage$/ ? ' selected="selected"' : '').
       '>'.$showtitle.'</option>'."\n";
    $ctr++;
       }
       $select.= '</select>';
   
     $result.='&nbsp;<input type="button" '.      $result.=
  'onClick="javascript:checkPickOne(this.form);"value="Next->" /><br />'."\n";          &Apache::lonhtmlcommon::row_title(&mt('Problems from'))
          .$select
          .&Apache::lonhtmlcommon::row_closure();
   
       $result.=
           &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
          .'<label><input type="radio" name="vProb" value="no"'
              .' checked="checked" /> '.&mt('no').' </label>'."\n"
          .'<label><input type="radio" name="vProb" value="yes" />'
              .&mt('yes').'</label>'."\n"
          .&Apache::lonhtmlcommon::row_closure();
   
       $result.=
           &Apache::lonhtmlcommon::row_title(&mt('View Submissions'))
          .'<label><input type="radio" name="lastSub" value="none" /> '
              .&mt('none').' </label>'."\n"
          .'<label><input type="radio" name="lastSub" value="datesub"'
              .' checked="checked" /> '.&mt('all submissions').'</label>'."\n"
          .'<label><input type="radio" name="lastSub" value="all" /> '
              .&mt('all submissions with details').' </label>'
          .&Apache::lonhtmlcommon::row_closure();
       
       $result.=
           &Apache::lonhtmlcommon::row_title(&mt('Use CODE'))
          .'<input type="text" name="CODE" value="" />'
          .&Apache::lonhtmlcommon::row_closure(1)
          .&Apache::lonhtmlcommon::end_pick_box();
   
       # Show list of students to select for grading
       $result.='<br /><input type="button" '.
                'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /><br />'."\n";
   
     $request->print($result);      $request->print($result);
   
     my $studentTable.='&nbsp;<b>Select a student you wish to grade and then click on the Next button.</b><br />'.      my $studentTable.='&nbsp;<b>'.&mt('Select a student you wish to grade and then click on the Next button.').'</b><br />'.
  '<table border="0"><tr><td bgcolor="#777777">'.   &Apache::loncommon::start_data_table().
  '<table border="0"><tr bgcolor="#e6ffff">'.   &Apache::loncommon::start_data_table_header_row().
  '<td align="right">&nbsp;<b>No.</b></td>'.   '<th align="right">&nbsp;'.&mt('No.').'</th>'.
  '<td>'.&nameUserString('header').'</td>'.   '<th>'.&nameUserString('header').'</th>'.
  '<td align="right">&nbsp;<b>No.</b></td>'.   '<th align="right">&nbsp;'.&mt('No.').'</th>'.
  '<td>'.&nameUserString('header').'</td></tr>';   '<th>'.&nameUserString('header').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
     
     my (undef,undef,$fullname) = &getclasslist($getsec,'1');      my (undef,undef,$fullname) = &getclasslist($getsec,'1',$getgroup);
     my $ptr = 1;      my $ptr = 1;
     foreach my $student (sort       foreach my $student (sort 
  {   {
Line 4074  LISTJAVASCRIPT Line 5025  LISTJAVASCRIPT
      return $a cmp $b;       return $a cmp $b;
  } (keys(%$fullname))) {   } (keys(%$fullname))) {
  my ($uname,$udom) = split(/:/,$student);   my ($uname,$udom) = split(/:/,$student);
  $studentTable.=($ptr%2 == 1 ? '<tr bgcolor="#ffffe6">' : '</td>');   $studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
                                     : '</td>');
  $studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';   $studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
  $studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '   $studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
     .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";      .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
  $studentTable.=($ptr%2 == 0 ? '</td></tr>' : '');   $studentTable.=
       ($ptr%2 == 0 ? '</td>'.&Apache::loncommon::end_data_table_row() 
                            : '');
  $ptr++;   $ptr++;
     }      }
     $studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td></tr>' if ($ptr%2 == 0);      if ($ptr%2 == 0) {
     $studentTable.='</table></td></tr></table>'."\n";   $studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td>'.
       &Apache::loncommon::end_data_table_row();
       }
       $studentTable.=&Apache::loncommon::end_data_table()."\n";
     $studentTable.='<input type="button" '.      $studentTable.='<input type="button" '.
  'onClick="javascript:checkPickOne(this.form);"value="Next->" /></form>'."\n";                     'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /></form>'."\n";
   
     $studentTable.=&show_grading_menu_form($symb);  
     $request->print($studentTable);      $request->print($studentTable);
   
     return '';      return '';
 }  }
   
 sub getSymbMap {  sub getSymbMap {
       my ($map_error) = @_;
     my $navmap = Apache::lonnavmaps::navmap->new();      my $navmap = Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           if (ref($map_error)) {
               $$map_error = 'navmap';
           }
           return;
       }
     my %symbx = ();      my %symbx = ();
     my @titles = ();      my @titles = ();
     my $minder = 0;      my $minder = 0;
Line 4103  sub getSymbMap { Line 5065  sub getSymbMap {
     my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },      my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
        1,0,1);         1,0,1);
     for my $sequence ($navmap->getById('0.0'), @sequences) {      for my $sequence ($navmap->getById('0.0'), @sequences) {
  if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {   if ($navmap->hasResource($sequence, sub { shift->is_gradable(); }, 0) ) {
     my $title = $minder.'.'.      my $title = $minder.'.'.
  &HTML::Entities::encode($sequence->compTitle(),'"\'&');   &HTML::Entities::encode($sequence->compTitle(),'"\'&');
     push(@titles, $title); # minder in case two titles are identical      push(@titles, $title); # minder in case two titles are identical
Line 4117  sub getSymbMap { Line 5079  sub getSymbMap {
 #  #
 #--- Displays a page/sequence w/wo problems, w/wo submissions  #--- Displays a page/sequence w/wo problems, w/wo submissions
 sub displayPage {  sub displayPage {
     my ($request) = shift;      my ($request,$symb) = @_;
   
     my ($symb) = &get_symb($request);  
     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'};
Line 4136  sub displayPage { Line 5096  sub displayPage {
     &Apache::lonnet::clear_EXT_cache_status();      &Apache::lonnet::clear_EXT_cache_status();
   
     if (!&canview($usec)) {      if (!&canview($usec)) {
  $request->print('<span class="LC_warning">Unable to view requested student.('.$env{'form.student'}.')</span>');          $request->print(
  $request->print(&show_grading_menu_form($symb));              '<span class="LC_warning">'.
  return;              &mt('Unable to view requested student. ([_1])',
                       $env{'form.student'}).
               '</span>');
           return;
     }      }
     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';      my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
     $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom).      $result.='<h3>&nbsp;'.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
  '</h3>'."\n";   '</h3>'."\n";
     if (&Apache::lonnet::validCODE($env{'form.CODE'})) {      $env{'form.CODE'} = uc($env{'form.CODE'});
  $result.='<h3>&nbsp;CODE: '.$env{'form.CODE'}.'</h3>'."\n";      if (&Apache::lonnet::validCODE(uc($env{'form.CODE'}))) {
    $result.='<h3>&nbsp;'.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
     } else {      } else {
  delete($env{'form.CODE'});   delete($env{'form.CODE'});
     }      }
Line 4152  sub displayPage { Line 5116  sub displayPage {
     $request->print($result);      $request->print($result);
   
     my $navmap = Apache::lonnavmaps::navmap->new();      my $navmap = Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $request->print(&navmap_errormsg());
           return;
       }
     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});      my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps      my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
     if (!$map) {      if (!$map) {
  $request->print('<span class="LC_warning">Unable to view requested sequence. ('.$resUrl.')</span>');   $request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
  $request->print(&show_grading_menu_form($symb));  
  return;    return; 
     }      }
     my $iterator = $navmap->getIterator($map->map_start(),      my $iterator = $navmap->getIterator($map->map_start(),
Line 4169  sub displayPage { Line 5136  sub displayPage {
  '<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".   '<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
  '<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".   '<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\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="overRideScore" value="no" />'."\n".   '<input type="hidden" name="overRideScore" value="no" />'."\n";
  '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";  
   
     if (defined($env{'form.CODE'})) {      if (defined($env{'form.CODE'})) {
  $studentTable.=   $studentTable.=
     '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";      '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
     }      }
     my $checkIcon = '<img alt="'.&mt('Check Mark').      my $checkIcon = '<img alt="'.&mt('Check Mark').
  '" src="'.$request->dir_config('lonIconsURL').   '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
  '/check.gif" height="16" border="0" />';  
   
     $studentTable.='&nbsp;<b>Note:</b> Problems graded correct by the computer are marked with a '.$checkIcon.      $studentTable.='&nbsp;<span class="LC_info">'.
  ' symbol.'."\n".          &mt('Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon).
  '<table border="0"><tr><td bgcolor="#777777">'.          '</span>'."\n".
  '<table border="0"><tr bgcolor="#e6ffff">'.   &Apache::loncommon::start_data_table().
  '<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.   &Apache::loncommon::start_data_table_header_row().
  '<td><b>&nbsp;'.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade</b></td></tr>';   '<th>'.&mt('Prob.').'</th>'.
    '<th>&nbsp;'.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
   
     &Apache::lonxml::clear_problem_counter();      &Apache::lonxml::clear_problem_counter();
     my ($depth,$question,$prob) = (1,1,1);      my ($depth,$question,$prob) = (1,1,1);
Line 4195  sub displayPage { Line 5162  sub displayPage {
         if($curRes == $iterator->BEGIN_MAP) { $depth++; }          if($curRes == $iterator->BEGIN_MAP) { $depth++; }
         if($curRes == $iterator->END_MAP) { $depth--; }          if($curRes == $iterator->END_MAP) { $depth--; }
   
         if (ref($curRes) && $curRes->is_problem()) {          if (ref($curRes) && $curRes->is_gradable()) {
     my $parts = $curRes->parts();      my $parts = $curRes->parts();
             my $title = $curRes->compTitle();              my $title = $curRes->compTitle();
     my $symbx = $curRes->symb();      my $symbx = $curRes->symb();
     $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.              my $is_tool = ($symbx =~ /ext\.tool$/);
  (scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).'&nbsp;parts)').'</td>';      $studentTable.=
    &Apache::loncommon::start_data_table_row().
    '<td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' 
                           : '<br />('.&mt('[_1]parts',
    scalar(@{$parts}).'&nbsp;').')'
    ).
    '</td>';
     $studentTable.='<td valign="top">';      $studentTable.='<td valign="top">';
     my %form = ('CODE' => $env{'form.CODE'},);      my %form = ('CODE' => $env{'form.CODE'},);
     if ($env{'form.vProb'} eq 'yes' ) {              if ($is_tool) {
  $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,                  $studentTable.='&nbsp;<b>'.$title.'</b><br />';
      undef,'both',\%form);              } else {
     } else {          if ($env{'form.vProb'} eq 'yes' ) {
  my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);      $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
  $companswer =~ s|<form(.*?)>||g;           undef,'both',\%form);
  $companswer =~ s|</form>||g;          } else {
 # while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>      my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
 #    $companswer =~ s/$1/ /ms;      $companswer =~ s|<form(.*?)>||g;
 #    $request->print('match='.$1."<br />\n");      $companswer =~ s|</form>||g;
 # }  #    while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
 # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;  #        $companswer =~ s/$1/ /ms;
  $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>Correct answer:</b><br />'.$companswer;  #        $request->print('match='.$1."<br />\n");
   #    }
   #    $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
       $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>'.&mt('Correct answer').':</b><br />'.$companswer;
    }
     }      }
   
     my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);      my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
   
     if ($env{'form.lastSub'} eq 'datesub') {      if ($env{'form.lastSub'} eq 'datesub') {
  if ($record{'version'} eq '') {   if ($record{'version'} eq '') {
     $studentTable.='<br />&nbsp;<span class="LC_warning">No recorded submission for this problem</span><br />';                      my $msg = &mt('No recorded submission for this problem.');
                       if ($is_tool) {
                           $msg = &mt('No recorded transactions for this external tool');
                       }
       $studentTable.='<br />&nbsp;<span class="LC_warning">'.$msg.'</span><br />';
  } else {   } else {
     my %responseType = ();      my %responseType = ();
     foreach my $partid (@{$parts}) {      foreach my $partid (@{$parts}) {
Line 4235  sub displayPage { Line 5217  sub displayPage {
  $responseType{$partid} = \%responseIds;   $responseType{$partid} = \%responseIds;
     }      }
     $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);      $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
   
  }   }
     } elsif ($env{'form.lastSub'} eq 'all') {      } elsif ($env{'form.lastSub'} eq 'all') {
  my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');   my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
                   my $identifier = (&canmodify($usec)? $prob : ''); 
  $studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,   $studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
  $env{'request.course.id'},   $env{'request.course.id'},
  '','.submission');   '','.submission',undef,
                                                                           $usec,$identifier);
     
     }      }
     if (&canmodify($usec)) {      if (&canmodify($usec)) {
               $studentTable.=&gradeBox_start();
  foreach my $partid (@{$parts}) {   foreach my $partid (@{$parts}) {
     $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);      $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
     $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";      $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
     $question++;      $question++;
  }   }
               $studentTable.=&gradeBox_end();
  $prob++;   $prob++;
     }      }
     $studentTable.='</td></tr>';      $studentTable.='</td></tr>';
Line 4258  sub displayPage { Line 5243  sub displayPage {
         $curRes = $iterator->next();          $curRes = $iterator->next();
     }      }
   
     $studentTable.='</table></td></tr></table>'."\n".      $studentTable.=
  '<input type="button" value="Save" '.          '</table>'."\n".
  'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.          '<input type="button" value="'.&mt('Save').'" '.
  '</form>'."\n";          'onclick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
     $studentTable.=&show_grading_menu_form($symb);          '</form>'."\n";
     $request->print($studentTable);      $request->print($studentTable);
   
     return '';      return '';
Line 4272  sub displaySubByDates { Line 5257  sub displaySubByDates {
     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;      my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
     my $isCODE=0;      my $isCODE=0;
     my $isTask = ($symb =~/\.task$/);      my $isTask = ($symb =~/\.task$/);
       my $is_tool = ($symb =~/\.tool$/);
     if (exists($record->{'resource.CODE'})) { $isCODE=1; }      if (exists($record->{'resource.CODE'})) { $isCODE=1; }
     my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'.      my $studentTable=&Apache::loncommon::start_data_table().
  '<table border="0" width="100%"><tr bgcolor="#e6ffff">'.   &Apache::loncommon::start_data_table_header_row().
  '<td><b>Date/Time</b></td>'.   '<th>'.&mt('Date/Time').'</th>'.
  ($isCODE?'<td><b>CODE</b></td>':'').   ($isCODE?'<th>'.&mt('CODE').'</th>':'').
  '<td><b>Submission</b></td>'.          ($isTask?'<th>'.&mt('Version').'</th>':'').
  '<td><b>Status&nbsp;</b></td></tr>';   '<th>'.($is_tool?&mt('Grade'):&mt('Submission')).'</th>'.
    '<th>'.&mt('Status').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
     my ($version);      my ($version);
     my %mark;      my %mark;
     my %orders;      my %orders;
     $mark{'correct_by_student'} = $checkIcon;      $mark{'correct_by_student'} = $checkIcon;
     if (!exists($$record{'1:timestamp'})) {      if (!exists($$record{'1:timestamp'})) {
  return '<br />&nbsp;<span class="LC_warning">Nothing submitted - no attempts</span><br />';          if ($is_tool) {
               return '<br />&nbsp;<span class="LC_warning">'.&mt('No grade passed back.').'</span><br />';
           } else {
               return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />';
           }
     }      }
   
     my $interaction;      my $interaction;
       my $no_increment = 1;
       my (%lastrndseed,%lasttype);
     for ($version=1;$version<=$$record{'version'};$version++) {      for ($version=1;$version<=$$record{'version'};$version++) {
  my $timestamp = scalar(localtime($$record{$version.':timestamp'}));   my $timestamp = 
       &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
  if (exists($$record{$version.':resource.0.version'})) {   if (exists($$record{$version.':resource.0.version'})) {
     $interaction = $$record{$version.':resource.0.version'};      $interaction = $$record{$version.':resource.0.version'};
  }   }
           if ($isTask && $env{'form.previousversion'}) {
               next unless ($interaction == $env{'form.previousversion'});
           }
  my $where = ($isTask ? "$version:resource.$interaction"   my $where = ($isTask ? "$version:resource.$interaction"
              : "$version:resource");               : "$version:resource");
  $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';   $studentTable.=&Apache::loncommon::start_data_table_row().
       '<td>'.$timestamp.'</td>';
  if ($isCODE) {   if ($isCODE) {
     $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';      $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
  }   }
           if ($isTask) {
               $studentTable.='<td>'.$interaction.'</td>';
           }
  my @versionKeys = split(/\:/,$$record{$version.':keys'});   my @versionKeys = split(/\:/,$$record{$version.':keys'});
  my @displaySub = ();   my @displaySub = ();
  foreach my $partid (@{$parts}) {   foreach my $partid (@{$parts}) {
     my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)              my ($hidden,$type);
             : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));              $type = $$record{$version.':resource.'.$partid.'.type'};
                   if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
                   $hidden = 1;
               }
               my @matchKey;
               if ($isTask) {
                   @matchKey = sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys);
               } elsif ($is_tool) {
                   @matchKey = sort(grep /^resource\.\Q$partid\E\.awarded$/,@versionKeys);
               } else {
                   @matchKey = sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys);
               }
 #    next if ($$record{"$version:resource.$partid.solved"} eq '');  #    next if ($$record{"$version:resource.$partid.solved"} eq '');
     my $display_part=&get_display_part($partid,$symb);      my $display_part=&get_display_part($partid,$symb);
     foreach my $matchKey (@matchKey) {      foreach my $matchKey (@matchKey) {
  if (exists($$record{$version.':'.$matchKey}) &&   if (exists($$record{$version.':'.$matchKey}) &&
     $$record{$version.':'.$matchKey} ne '') {      $$record{$version.':'.$matchKey} ne '') {
                       if ($is_tool) {
     my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)                          $displaySub[0].=$$record{"$version:resource.$partid.awarded"};
                : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));                      } else {
     $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';          my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
     $displaySub[0].='<span class="LC_internal_info">(ID&nbsp;'.                     : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
  $responseId.')</span>&nbsp;<b>';                          $displaySub[0].='<span class="LC_nobreak">';
     if ($$record{"$where.$partid.tries"} eq '') {                          $displaySub[0].='<b>'.&mt('Part: [_1]',$display_part).'</b>'
  $displaySub[0].='Trial&nbsp;not&nbsp;counted';                                         .' <span class="LC_internal_info">'
     } else {                                         .'('.&mt('Response ID: [_1]',$responseId).')'
  $displaySub[0].='Trial&nbsp;'.                                         .'</span>'
     $$record{"$where.$partid.tries"};                                         .' <b>';
     }                          if ($hidden) {
     my $responseType=($isTask ? 'Task'                              $displaySub[0].= &mt('Anonymous Survey').'</b>';
                           } else {
                               my ($trial,$rndseed,$newvariation);
                               if ($type eq 'randomizetry') {
                                   $trial = $$record{"$where.$partid.tries"};
                                   $rndseed = $$record{"$where.$partid.rndseed"};
                               }
               if ($$record{"$where.$partid.tries"} eq '') {
           $displaySub[0].=&mt('Trial not counted');
               } else {
           $displaySub[0].=&mt('Trial: [_1]',
           $$record{"$where.$partid.tries"});
                                   if (($rndseed ne '') && ($lastrndseed{$partid} ne '')) {
                                       if (($rndseed ne $lastrndseed{$partid}) &&
                                           (($type eq 'randomizetry') || ($lasttype{$partid} eq 'randomizetry'))) {
                                           $newvariation = '&nbsp;('.&mt('New variation this try').')';
                                       }
                                   }
                                   $lastrndseed{$partid} = $rndseed;
                                   $lasttype{$partid} = $type;
               }
               my $responseType=($isTask ? 'Task'
                                               : $responseType->{$partid}->{$responseId});                                                : $responseType->{$partid}->{$responseId});
     if (!exists($orders{$partid})) { $orders{$partid}={}; }              if (!exists($orders{$partid})) { $orders{$partid}={}; }
     if (!exists($orders{$partid}->{$responseId})) {              if ((!exists($orders{$partid}->{$responseId})) || ($trial)) {
  $orders{$partid}->{$responseId}=          $orders{$partid}->{$responseId}=
     &get_order($partid,$responseId,$symb,$uname,$udom);              &get_order($partid,$responseId,$symb,$uname,$udom,
     }                                                 $no_increment,$type,$trial,$rndseed);
     $displaySub[0].='</b>&nbsp; '.              }
  &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />';              $displaySub[0].='</b>'.$newvariation.'</span>'; # /nobreak
               $displaySub[0].='&nbsp; '.
           &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom,$type,$trial,$rndseed).'<br />';
                           }
                       }
  }   }
     }      }
     if (exists($$record{"$where.$partid.checkedin"})) {      if (exists($$record{"$where.$partid.checkedin"})) {
  $displaySub[1].='Checked in by '.   $displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
     $$record{"$where.$partid.checkedin"}.' into slot '.      $$record{"$where.$partid.checkedin"},
     $$record{"$where.$partid.checkedin.slot"}.      $$record{"$where.$partid.checkedin.slot"}).
     '<br />';   '<br />';
     }      }
     if (exists $$record{"$where.$partid.award"}) {      if (exists $$record{"$where.$partid.award"}) {
  $displaySub[1].='<b>Part:</b>&nbsp;'.$display_part.' &nbsp;'.   $displaySub[1].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.' &nbsp;'.
     lc($$record{"$where.$partid.award"}).' '.      lc($$record{"$where.$partid.award"}).' '.
     $mark{$$record{"$where.$partid.solved"}}.      $mark{$$record{"$where.$partid.solved"}}.
     '<br />';      '<br />';
       } elsif (($is_tool) && (exists($$record{"$version:resource.$partid.solved"}))) {
    if ($$record{"$version:resource.$partid.solved"} =~ /^(in|)correct_by_passback$/) {
       $displaySub[1].=&mt('Grade passed back by external tool');
    }
     }      }
     if (exists $$record{"$where.$partid.regrader"}) {      if (exists $$record{"$where.$partid.regrader"}) {
  $displaySub[2].=$$record{"$where.$partid.regrader"}.   $displaySub[2].=$$record{"$where.$partid.regrader"};
     ' (<b>'.&mt('Part').':</b> '.$display_part.')';   unless ($is_tool) {
       $displaySub[2].=' (<b>'.&mt('Part').':</b> '.$display_part.')';
    }
     } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {      } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
  $displaySub[2].=   $displaySub[2].=
     $$record{"$version:resource.$partid.regrader"}.      $$record{"$version:resource.$partid.regrader"};
     ' (<b>'.&mt('Part').':</b> '.$display_part.')';                  unless ($is_tool) {
       $displaySub[2].=' (<b>'.&mt('Part').':</b> '.$display_part.')';
                   }
     }      }
  }   }
  # needed because old essay regrader has not parts info   # needed because old essay regrader has not parts info
Line 4362  sub displaySubByDates { Line 5405  sub displaySubByDates {
  }   }
  $studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];   $studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
  if ($displaySub[2]) {   if ($displaySub[2]) {
     $studentTable.='Manually graded by '.$displaySub[2];      $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
  }   }
  $studentTable.='&nbsp;</td></tr>';   $studentTable.='&nbsp;</td>'.
           &Apache::loncommon::end_data_table_row();
     }      }
     $studentTable.='</table></td></tr></table>';      $studentTable.=&Apache::loncommon::end_data_table();
     return $studentTable;      return $studentTable;
 }  }
   
 sub updateGradeByPage {  sub updateGradeByPage {
     my ($request) = shift;      my ($request,$symb) = @_;
   
     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 4382  sub updateGradeByPage { Line 5425  sub updateGradeByPage {
     my ($uname,$udom) = split(/:/,$env{'form.student'});      my ($uname,$udom) = split(/:/,$env{'form.student'});
     my $usec=$classlist->{$env{'form.student'}}[5];      my $usec=$classlist->{$env{'form.student'}}[5];
     if (!&canmodify($usec)) {      if (!&canmodify($usec)) {
  $request->print('<span class="LC_warning">Unable to modify requested student.('.$env{'form.student'}.'</span>');   $request->print('<span class="LC_warning">'.&mt('Unable to modify requested student ([_1])',$env{'form.student'}).'</span>');
  $request->print(&show_grading_menu_form($env{'form.symb'}));  
  return;   return;
     }      }
     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';      my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
     $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).      $result.='<h3>&nbsp;'.&mt('Student: ').&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
  '</h3>'."\n";   '</h3>'."\n";
   
     $request->print($result);      $request->print($result);
   
   
     my $navmap = Apache::lonnavmaps::navmap->new();      my $navmap = Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $request->print(&navmap_errormsg());
           return;
       }
     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});      my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps      my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
     if (!$map) {      if (!$map) {
  $request->print('<span class="LC_warning">Unable to grade requested sequence. ('.$resUrl.')</span>');   $request->print('<span class="LC_warning">'.&mt('Unable to grade requested sequence ([_1]).',$resUrl).'</span>');
  my ($symb)=&get_symb($request);  
  $request->print(&show_grading_menu_form($symb));  
  return;    return; 
     }      }
     my $iterator = $navmap->getIterator($map->map_start(),      my $iterator = $navmap->getIterator($map->map_start(),
  $map->map_finish());   $map->map_finish());
   
     my $studentTable='<table border="0"><tr><td bgcolor="#777777">'.      my $studentTable=
  '<table border="0"><tr bgcolor="#e6ffff">'.   &Apache::loncommon::start_data_table().
  '<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.   &Apache::loncommon::start_data_table_header_row().
  '<td><b>&nbsp;Title&nbsp;</b></td>'.   '<th align="center">&nbsp;'.&mt('Prob.').'&nbsp;</th>'.
  '<td><b>&nbsp;Previous Score&nbsp;</b></td>'.   '<th>&nbsp;'.&mt('Title').'&nbsp;</th>'.
  '<td><b>&nbsp;New Score&nbsp;</b></td></tr>';   '<th>&nbsp;'.&mt('Previous Score').'&nbsp;</th>'.
    '<th>&nbsp;'.&mt('New Score').'&nbsp;</th>'.
    &Apache::loncommon::end_data_table_header_row();
   
     $iterator->next(); # skip the first BEGIN_MAP      $iterator->next(); # skip the first BEGIN_MAP
     my $curRes = $iterator->next(); # for "current resource"      my $curRes = $iterator->next(); # for "current resource"
     my ($depth,$question,$prob,$changeflag)= (1,1,1,0);      my ($depth,$question,$prob,$changeflag,$hideflag)= (1,1,1,0,0);
     while ($depth > 0) {      while ($depth > 0) {
         if($curRes == $iterator->BEGIN_MAP) { $depth++; }          if($curRes == $iterator->BEGIN_MAP) { $depth++; }
         if($curRes == $iterator->END_MAP) { $depth--; }          if($curRes == $iterator->END_MAP) { $depth--; }
Line 4422  sub updateGradeByPage { Line 5469  sub updateGradeByPage {
     my $parts = $curRes->parts();      my $parts = $curRes->parts();
             my $title = $curRes->compTitle();              my $title = $curRes->compTitle();
     my $symbx = $curRes->symb();      my $symbx = $curRes->symb();
     $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.      $studentTable.=
  (scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).'&nbsp;parts)').'</td>';   &Apache::loncommon::start_data_table_row().
    '<td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' 
                                           : '<br />('.&mt('[quant,_1,part]',scalar(@{$parts}))
    .')').'</td>';
     $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';      $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
   
     my %newrecord=();      my %newrecord=();
     my @displayPts=();      my @displayPts=();
             my %aggregate = ();              my %aggregate = ();
             my $aggregateflag = 0;              my $aggregateflag = 0;
               if ($env{'form.HIDE'.$prob}) {
                   my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
                   my ($version,$parts) = split(/:/,$env{'form.HIDE'.$prob},2);
                   my $numchgs = &makehidden($version,$parts,\%record,$symbx,$udom,$uname,1);
                   $hideflag += $numchgs;
               }
     foreach my $partid (@{$parts}) {      foreach my $partid (@{$parts}) {
  my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};   my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
  my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};   my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
Line 4467  sub updateGradeByPage { Line 5524  sub updateGradeByPage {
  }   }
  my $display_part=&get_display_part($partid,$curRes->symb());   my $display_part=&get_display_part($partid,$curRes->symb());
  my $oldstatus = $env{'form.solved'.$question.'_'.$partid};   my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
  $displayPts[0].='&nbsp;<b>Part:</b> '.$display_part.' = '.   $displayPts[0].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
     (($oldstatus eq 'excused') ? 'excused' : $oldpts).      (($oldstatus eq 'excused') ? 'excused' : $oldpts).
     '&nbsp;<br />';      '&nbsp;<br />';
  $displayPts[1].='&nbsp;<b>Part:</b> '.$display_part.' = '.   $displayPts[1].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
      (($score eq 'excused') ? 'excused' : $newpts).       (($score eq 'excused') ? 'excused' : $newpts).
     '&nbsp;<br />';      '&nbsp;<br />';
  $question++;   $question++;
Line 4510  sub updateGradeByPage { Line 5567  sub updateGradeByPage {
   
     $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.      $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
  '<td valign="top">'.$displayPts[1].'</td>'.   '<td valign="top">'.$displayPts[1].'</td>'.
  '</tr>';   &Apache::loncommon::end_data_table_row();
   
     $prob++;      $prob++;
  }   }
         $curRes = $iterator->next();          $curRes = $iterator->next();
     }      }
   
     $studentTable.='</td></tr></table></td></tr></table>';      $studentTable.=&Apache::loncommon::end_data_table();
     $studentTable.=&show_grading_menu_form($env{'form.symb'});      my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
     my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :    &mt('The scores were changed for [quant,_1,problem].',
   'The scores were changed for '.    $changeflag).'<br />');
   $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));      my $hidemsg=($hideflag == 0 ? '' :
     $request->print($grademsg.$studentTable);                   &mt('Submissions were marked "hidden" for [quant,_1,transaction].',
                        $hideflag).'<br />');
       $request->print($hidemsg.$grademsg.$studentTable);
   
     return '';      return '';
 }  }
Line 4531  sub updateGradeByPage { Line 5590  sub updateGradeByPage {
 #  #
 #-------------------------------------------------------------------  #-------------------------------------------------------------------
   
 #--------------------Scantron Grading-----------------------------------  #-------------------- Bubblesheet (Scantron) Grading -------------------
 #  #
 #------ start of section for handling grading by page/sequence ---------  #------ start of section for handling grading by page/sequence ---------
   
Line 4545  sub updateGradeByPage { Line 5604  sub updateGradeByPage {
    from the file that we are parsing that represents one entire sheet     from the file that we are parsing that represents one entire sheet
   
    'bubble line' refers to the data     'bubble line' refers to the data
    representing the line of bubbles that are on the physical bubble sheet     representing the line of bubbles that are on the physical bubblesheet
   
   
 The overall process is that a scanned in bubble sheet data is uploaded  The overall process is that a scanned in bubblesheet data is uploaded
 into a course. When a user wants to grade, they select a  into a course. When a user wants to grade, they select a
 sequence/folder of resources, a file of bubble sheet info, and pick  sequence/folder of resources, a file of bubblesheet info, and pick
 one of the predefined configurations for what each scanline looks  one of the predefined configurations for what each scanline looks
 like.  like.
   
 Next each scanline is checked for any errors of either 'missing  Next each scanline is checked for any errors of either 'missing
 bubbles' (it's an error because it may have been mis-scanned  bubbles' (it's an error because it may have been mis-scanned
 because too light bubbling), 'double bubble' (each bubble line should  because too light bubbling), 'double bubble' (each bubble line should
 have no more that one letter picked), invalid or duplicated CODE,  have no more than one letter picked), invalid or duplicated CODE,
 invalid student ID  invalid student/employee ID
   
 If the CODE option is used that determines the randomization of the  If the CODE option is used that determines the randomization of the
 homework problems, either way the student ID is looked up into a  homework problems, either way the student/employee ID is looked up into a
 username:domain.  username:domain.
   
 During the validation phase the instructor can choose to skip scanlines.   During the validation phase the instructor can choose to skip scanlines. 
   
 After the validation phase, there are now 3 bubble sheet files  After the validation phase, there are now 3 bubblesheet files
   
   scantron_original_filename (unmodified original file)    scantron_original_filename (unmodified original file)
   scantron_corrected_filename (file where the corrected information has replaced the original information)    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)    scantron_skipped_filename (contains the exact text of scanlines that where skipped)
   
 Also there is a separate hash nohist_scantrondata that contains extra  Also there is a separate hash nohist_scantrondata that contains extra
 correction information that isn't representable in the bubble sheet  correction information that isn't representable in the bubblesheet
 file (see &scantron_getfile() for more information)  file (see &scantron_getfile() for more information)
   
 After all scanlines are either valid, marked as valid or skipped, then  After all scanlines are either valid, marked as valid or skipped, then
Line 4596  the homework problem. Line 5655  the homework problem.
   
 sub defaultFormData {  sub defaultFormData {
     my ($symb)=@_;      my ($symb)=@_;
     return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".      return '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />';
      '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".  
      '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";  
 }  }
   
   
Line 4609  sub defaultFormData { Line 5666  sub defaultFormData {
    Return html dropdown of possible sequences to grade     Return html dropdown of possible sequences to grade
     
  Arguments:   Arguments:
    $symb - $symb of the current resource      $symb - $symb of the current resource
      $map_error - ref to scalar which will container error if
                   $navmap object is unavailable in &getSymbMap().
   
 =cut  =cut
   
 sub getSequenceDropDown {  sub getSequenceDropDown {
     my ($symb)=@_;      my ($symb,$map_error)=@_;
     my $result='<select name="selectpage">'."\n";      my $result='<select name="selectpage">'."\n";
     my ($titles,$symbx) = &getSymbMap();      my ($titles,$symbx) = &getSymbMap($map_error);
       if (ref($map_error)) {
           return if ($$map_error);
       }
     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 4630  sub getSequenceDropDown { Line 5692  sub getSequenceDropDown {
     return $result;      return $result;
 }  }
   
   my %bubble_lines_per_response;     # no. bubble lines for each response.
                                      # key is zero-based index - 0, 1, 2 ...
   
   my %first_bubble_line;             # First bubble line no. for each bubble.
   
   my %subdivided_bubble_lines;       # no. bubble lines for optionresponse, 
                                      # matchresponse or rankresponse, where 
                                      # an individual response can have multiple 
                                      # lines
   
   my %responsetype_per_response;     # responsetype for each response
   
   my %masterseq_id_responsenum;      # src_id (e.g., 12.3_0.11 etc.) for each
                                      # numbered response. Needed when randomorder
                                      # or randompick are in use. Key is ID, value 
                                      # is response number.
   
   # Save and restore the bubble lines array to the form env.
   
   
   sub save_bubble_lines {
       foreach my $line (keys(%bubble_lines_per_response)) {
    $env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
    $env{"form.scantron.first_bubble_line.$line"} =
       $first_bubble_line{$line};
           $env{"form.scantron.sub_bubblelines.$line"} = 
               $subdivided_bubble_lines{$line};
           $env{"form.scantron.responsetype.$line"} =
               $responsetype_per_response{$line};
       }
       foreach my $resid (keys(%masterseq_id_responsenum)) {
           my $line = $masterseq_id_responsenum{$resid};
           $env{"form.scantron.residpart.$line"} = $resid;
       }
   }
   
   
   sub restore_bubble_lines {
       my $line = 0;
       %bubble_lines_per_response = ();
       %masterseq_id_responsenum = ();
       while ($env{"form.scantron.bubblelines.$line"}) {
    my $value = $env{"form.scantron.bubblelines.$line"};
    $bubble_lines_per_response{$line} = $value;
    $first_bubble_line{$line}  =
       $env{"form.scantron.first_bubble_line.$line"};
           $subdivided_bubble_lines{$line} =
               $env{"form.scantron.sub_bubblelines.$line"};
           $responsetype_per_response{$line} =
               $env{"form.scantron.responsetype.$line"};
           my $id = $env{"form.scantron.residpart.$line"};
           $masterseq_id_responsenum{$id} = $line;
    $line++;
       }
   }
   
 =pod   =pod 
   
Line 4642  sub getSequenceDropDown { Line 5759  sub getSequenceDropDown {
 sub scantron_filenames {  sub scantron_filenames {
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,      my $getpropath = 1;
     &propath($cdom,$cname));      my ($dirlist,$listerror) = &Apache::lonnet::dirlist('userfiles',$cdom,
                                                           $cname,$getpropath);
     my @possiblenames;      my @possiblenames;
     foreach my $filename (sort(@files)) {      if (ref($dirlist) eq 'ARRAY') {
  ($filename)=split(/&/,$filename);          foreach my $filename (sort(@{$dirlist})) {
  if ($filename!~/^scantron_orig_/) { next ; }      ($filename)=split(/&/,$filename);
  $filename=~s/^scantron_orig_//;      if ($filename!~/^scantron_orig_/) { next ; }
  push(@possiblenames,$filename);      $filename=~s/^scantron_orig_//;
       push(@possiblenames,$filename);
           }
     }      }
     return @possiblenames;      return @possiblenames;
 }  }
Line 4686  sub scantron_uploads { Line 5806  sub scantron_uploads {
 =cut  =cut
   
 sub scantron_scantab {  sub scantron_scantab {
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');  
     my $result='<select name="scantron_format">'."\n";      my $result='<select name="scantron_format">'."\n";
     $result.='<option></option>'."\n";      $result.='<option></option>'."\n";
     foreach my $line (<$fh>) {      my @lines = &Apache::lonnet::get_scantronformat_file();
  my ($name,$descrip)=split(/:/,$line);      if (@lines > 0) {
  if ($name =~ /^\#/) { next; }          foreach my $line (@lines) {
  $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";              next if (($line =~ /^\#/) || ($line eq ''));
       my ($name,$descrip)=split(/:/,$line);
       $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
           }
     }      }
     $result.='</select>'."\n";      $result.='</select>'."\n";
   
     return $result;      return $result;
 }  }
   
Line 4731  sub scantron_CODElist { Line 5852  sub scantron_CODElist {
 =cut  =cut
   
 sub scantron_CODEunique {  sub scantron_CODEunique {
     my $result='<span style="white-space: nowrap;">      my $result='<span class="LC_nobreak">
                  <label><input type="radio" name="scantron_CODEunique"                   <label><input type="radio" name="scantron_CODEunique"
                         value="yes" checked="checked" />'.&mt('Yes').' </label>                          value="yes" checked="checked" />'.&mt('Yes').' </label>
                 </span>                  </span>
                 <span style="white-space: nowrap;">                  <span class="LC_nobreak">
                  <label><input type="radio" name="scantron_CODEunique"                   <label><input type="radio" name="scantron_CODEunique"
                         value="no" />'.&mt('No').' </label>                          value="no" />'.&mt('No').' </label>
                 </span>';                  </span>';
Line 4746  sub scantron_CODEunique { Line 5867  sub scantron_CODEunique {
   
 =item scantron_selectphase  =item scantron_selectphase
   
   Generates the initial screen to start the bubble sheet process.    Generates the initial screen to start the bubblesheet process.
   Allows for - starting a grading run.    Allows for - starting a grading run.
              - downloading existing scan data (original, corrected               - downloading existing scan data (original, corrected
                                                 or skipped info)                                                  or skipped info)
Line 4760  sub scantron_CODEunique { Line 5881  sub scantron_CODEunique {
 =cut  =cut
   
 sub scantron_selectphase {  sub scantron_selectphase {
     my ($r,$file2grade) = @_;      my ($r,$file2grade,$symb) = @_;
     my ($symb)=&get_symb($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $sequence_selector=&getSequenceDropDown($symb);      my $map_error;
       my $sequence_selector=&getSequenceDropDown($symb,\$map_error);
       if ($map_error) {
           $r->print('<br />'.&navmap_errormsg().'<br />');
           return;
       }
     my $default_form_data=&defaultFormData($symb);      my $default_form_data=&defaultFormData($symb);
     my $grading_menu_button=&show_grading_menu_form($symb);  
     my $file_selector=&scantron_uploads($file2grade);      my $file_selector=&scantron_uploads($file2grade);
     my $format_selector=&scantron_scantab();      my $format_selector=&scantron_scantab();
     my $CODE_selector=&scantron_CODElist();      my $CODE_selector=&scantron_CODElist();
     my $CODE_unique=&scantron_CODEunique();      my $CODE_unique=&scantron_CODEunique();
     my $result;      my $result;
   
     # Chunk of form to prompt for a file to grade and how:      $ssi_error = 0;
   
     $result.= <<SCANTRONFORM;  
     <table width="100%" border="0">  
     <tr>  
      <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">  
       <td bgcolor="#777777">  
        <input type="hidden" name="command" value="scantron_warning" />  
         $default_form_data  
         <table width="100%" border="0">  
           <tr bgcolor="#e6ffff">  
             <td colspan="2">  
               &nbsp;<b>Specify file and which Folder/Sequence to grade</b>  
             </td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
             <td> Sequence to grade: </td><td> $sequence_selector </td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
             <td> Filename of scoring office file: </td><td> $file_selector </td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
             <td> Format of data file: </td><td> $format_selector </td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
             <td> Saved CODEs to validate against: </td><td> $CODE_selector</td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
             <td> Each CODE is only to be used once:</td><td> $CODE_unique </td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
     <td> Options: </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_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>  
     </td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
             <td colspan="2">  
               <input type="submit" value="Grading: Validate Scantron Records" />  
             </td>  
           </tr>  
         </table>  
        </td>  
      </form>  
     </tr>  
 SCANTRONFORM  
      
     $r->print($result);  
   
     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.   # Chunk of form to prompt for a scantron file upload.
   
         $r->print(<<SCANTRONFORM);          $r->print('
     <tr>      <br />');
       <td bgcolor="#777777">  
         <table width="100%" border="0">  
           <tr bgcolor="#e6ffff">  
             <td>  
               &nbsp;<b>Specify a Scantron data file to upload.</b>  
             </td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
             <td>  
 SCANTRONFORM  
     my $default_form_data=&defaultFormData(&get_symb($r,1));  
     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};      my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};      my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
     $r->print(<<UPLOAD);      my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
               <script type="text/javascript" language="javascript">      &js_escape(\$alertmsg);
       my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($cdom);
       $r->print(&Apache::lonhtmlcommon::scripttag('
     function checkUpload(formname) {      function checkUpload(formname) {
  if (formname.upfile.value == "") {   if (formname.upfile.value == "") {
     alert("Please use the browse button to select a file from your local directory.");      alert("'.$alertmsg.'");
     return false;      return false;
  }   }
  formname.submit();   formname.submit();
       }'."\n".$formatjs));
       $r->print('
                 <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
                   '.$default_form_data.'
                   <input name="courseid" type="hidden" value="'.$cnum.'" />
                   <input name="domainid" type="hidden" value="'.$cdom.'" />
                   <input name="command" value="scantronupload_save" type="hidden" />
                 '.&Apache::loncommon::start_data_table('LC_scantron_action').'
                 '.&Apache::loncommon::start_data_table_header_row().'
                   <th>
                   &nbsp;'.&mt('Specify a bubblesheet data file to upload.').'
                   </th>
                 '.&Apache::loncommon::end_data_table_header_row().'
                 '.&Apache::loncommon::start_data_table_row().'
               <td>
                   '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'<br />'."\n");
       if ($formatoptions) {
           $r->print('</td>
                    '.&Apache::loncommon::end_data_table_row().'
                    '.&Apache::loncommon::start_data_table_row().'
                    <td>'.$formattitle.('&nbsp;'x2).$formatoptions.'
                    </td>
                    '.&Apache::loncommon::end_data_table_row().'
                    '.&Apache::loncommon::start_data_table_row().'
                    <td>'
           );
       } else {
           $r->print(' <br />');
     }      }
               </script>      $r->print('<input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
   
               <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>  
                 $default_form_data  
                 <input name='courseid' type='hidden' value='$cnum' />  
                 <input name='domainid' type='hidden' value='$cdom' />  
                 <input name='command' value='scantronupload_save' type='hidden' />  
                 File to upload:<input type="file" name="upfile" size="50" />  
                 <br />  
                 <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />  
               </form>  
 UPLOAD  
   
         $r->print(<<SCANTRONFORM);  
             </td>  
           </tr>  
         </table>  
       </td>  
     </tr>  
 SCANTRONFORM  
     }  
   
     # Chunk of the form that prompts to view a scoring office file,  
     # corrected file, skipped records in a file.  
   
     $r->print(<<SCANTRONFORM);  
     <tr>  
       <form action='/adm/grades' name='scantron_download'>  
         <td bgcolor="#777777">  
   $default_form_data  
           <input type="hidden" name="command" value="scantron_download" />  
           <table width="100%" border="0">  
             <tr bgcolor="#e6ffff">  
               <td colspan="2">  
                 &nbsp;<b>Download a scoring office file</b>  
               </td>                </td>
             </tr>               '.&Apache::loncommon::end_data_table_row().'
             <tr bgcolor="#ffffe6">               '.&Apache::loncommon::end_data_table().'
               <td> Filename of scoring office file: </td><td> $file_selector </td>               </form>'
             </tr>      );
             <tr bgcolor="#ffffe6">  
               <td colspan="2">  
                 <input type="submit" value="Download: Show List of Associated Files" />  
               </td>  
             </tr>  
           </table>  
         </td>  
       </form>  
     </tr>  
 SCANTRONFORM  
   
     $r->print('<tr><td bgcolor="#777777">');      }
     &Apache::lonpickcode::code_list($r,2);  
     $r->print('</td></tr></table>');  
     $r->print($grading_menu_button);  
     return  
 }  
   
 =pod      # Chunk of form to prompt for a file to grade and how:
   
 =item get_scantron_config      $result.= '
       <br />
       <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
       <input type="hidden" name="command" value="scantron_warning" />
       '.$default_form_data.'
       '.&Apache::loncommon::start_data_table('LC_scantron_action').'
          '.&Apache::loncommon::start_data_table_header_row().'
               <th colspan="2">
                 &nbsp;'.&mt('Specify file and which Folder/Sequence to grade').'
               </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> '.&mt('Filename of bubblesheet data file:').' </td><td> '.$file_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> '.&mt('Format of bubblesheet data file:').' </td><td> '.$format_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> '.&mt('Each CODE is only to be used once:').'</td><td> '.$CODE_unique.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
       <td> '.&mt('Options:').' </td>
               <td>
          <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> '.&mt('Do only previously skipped records').'</label> <br />
                  <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> '.&mt('Remove all existing corrections').'</label> <br />
                  <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources when grading').'</label>
       </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td colspan="2">
                 <input type="submit" value="'.&mt('Grading: Validate Bubblesheet Records').'" />
               </td>
          '.&Apache::loncommon::end_data_table_row().'
       '.&Apache::loncommon::end_data_table().'
       </form>
   ';
      
       $r->print($result);
   
    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.  
   
       # Chunk of the form that prompts to view a scoring office file,
       # corrected file, skipped records in a file.
   
  Returns:      $r->print('
             If the named configuration is not in the file, an empty     <br />
             hash is returned.     <form action="/adm/grades" name="scantron_download">
     a hash with the fields       '.$default_form_data.'
       name         - internal name for the this configuration setup       <input type="hidden" name="command" value="scantron_download" />
       description  - text to display to operator that describes this config       '.&Apache::loncommon::start_data_table('LC_scantron_action').'
       CODElocation - if 0 or the string 'none'         '.&Apache::loncommon::start_data_table_header_row().'
                           - no CODE exists for this config                <th>
                      if -1 || the string 'letter'                  &nbsp;'.&mt('Download a scoring office file').'
                           - a CODE exists for this config and is                </th>
                             a string of letters         '.&Apache::loncommon::end_data_table_header_row().'
                      Unsupported value (but planned for future support)         '.&Apache::loncommon::start_data_table_row().'
                           if a positive integer                <td> '.&mt('Filename of scoring office file: [_1]',$file_selector).' 
                                - The CODE exists as the first n items from                  <br />
                                  the question section of the form                  <input type="submit" value="'.&mt('Download: Show List of Associated Files').'" />
                           if the string 'number'         '.&Apache::loncommon::end_data_table_row().'
                                - The CODE exists for this config and is       '.&Apache::loncommon::end_data_table().'
                                  a string of numbers     </form>
       CODEstart   - (only matter if a CODE exists) column in the line where     <br />
                      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      &Apache::lonpickcode::code_list($r,2);
   
 sub get_scantron_config {      $r->print('<br /><form method="post" name="checkscantron" action="">'.
     my ($which) = @_;               $default_form_data."\n".
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');               &Apache::loncommon::start_data_table('LC_scantron_action')."\n".
     my %config;               &Apache::loncommon::start_data_table_header_row()."\n".
     #FIXME probably should move to XML it has already gotten a bit much now               '<th colspan="2">
     foreach my $line (<$fh>) {                &nbsp;'.&mt('Review bubblesheet data and submissions for a previously graded folder/sequence')."\n".
  my ($name,$descrip)=split(/:/,$line);               '</th>'."\n".
  if ($name ne $which ) { next; }                &Apache::loncommon::end_data_table_header_row()."\n".
  chomp($line);                &Apache::loncommon::start_data_table_row()."\n".
  my @config=split(/:/,$line);                '<td> '.&mt('Graded folder/sequence:').' </td>'."\n".
  $config{'name'}=$config[0];                '<td> '.$sequence_selector.' </td>'.
  $config{'description'}=$config[1];                &Apache::loncommon::end_data_table_row()."\n".
  $config{'CODElocation'}=$config[2];                &Apache::loncommon::start_data_table_row()."\n".
  $config{'CODEstart'}=$config[3];                '<td> '.&mt('Filename of scoring office file:').' </td>'."\n".
  $config{'CODElength'}=$config[4];                '<td> '.$file_selector.' </td>'."\n".
  $config{'IDstart'}=$config[5];                &Apache::loncommon::end_data_table_row()."\n".
  $config{'IDlength'}=$config[6];                &Apache::loncommon::start_data_table_row()."\n".
  $config{'Qstart'}=$config[7];                '<td> '.&mt('Format of data file:').' </td>'."\n".
  $config{'Qlength'}=$config[8];                '<td> '.$format_selector.' </td>'."\n".
  $config{'Qoff'}=$config[9];                &Apache::loncommon::end_data_table_row()."\n".
  $config{'Qon'}=$config[10];                &Apache::loncommon::start_data_table_row()."\n".
  $config{'PaperID'}=$config[11];                '<td> '.&mt('Options').' </td>'."\n".
  $config{'PaperIDlength'}=$config[12];                '<td> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources').'</label></td>'.
  $config{'FirstName'}=$config[13];                &Apache::loncommon::end_data_table_row()."\n".
  $config{'FirstNamelength'}=$config[14];                &Apache::loncommon::start_data_table_row()."\n".
  $config{'LastName'}=$config[15];                '<td colspan="2">'."\n".
  $config{'LastNamelength'}=$config[16];                '<input type="hidden" name="command" value="checksubmissions" />'."\n".
  last;                '<input type="submit" value="'.&mt('Review Bubblesheet Data and Submission Records').'" />'."\n".
     }                '</td>'."\n".
     return %config;                &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::end_data_table()."\n".
                 '</form><br />');
       return;
 }  }
   
 =pod   =pod 
   
 =item username_to_idmap  =item username_to_idmap
   
     creates a hash keyed by student id with values of the corresponding      creates a hash keyed by student/employee ID with values of the corresponding
     student username:domain.      student username:domain. If a single ID occurs for more than one student,
       the status of the student is checked, and if Active, the value in the hash
       will be set to the Active student.
   
   Arguments:    Arguments:
   
Line 5022  sub username_to_idmap { Line 6090  sub username_to_idmap {
     my ($classlist)= @_;      my ($classlist)= @_;
     my %idmap;      my %idmap;
     foreach my $student (keys(%$classlist)) {      foreach my $student (keys(%$classlist)) {
  $idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}=          my $id = $classlist->{$student}->[&Apache::loncoursedata::CL_ID];
     $student;          unless ($id eq '') {
               if (!exists($idmap{$id})) {
                   $idmap{$id} = $student;
               } else {
                   my $status = $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS];
                   if ($status eq 'Active') {
                       $idmap{$id} = $student;
                   }
               }
           }
     }      }
     return %idmap;      return %idmap;
 }  }
Line 5035  sub username_to_idmap { Line 6112  sub username_to_idmap {
    Process a requested correction to a scanline.     Process a requested correction to a scanline.
   
   Arguments:    Arguments:
     $scantron_config   - hash from &get_scantron_config()      $scantron_config   - hash from &Apache::lonnet::get_scantron_config()
     $scan_data         - hash of correction information       $scan_data         - hash of correction information 
                           (see &scantron_getfile())                            (see &scantron_getfile())
     $line              - existing scanline      $line              - existing scanline
     $whichline         - line number of the passed in scanline      $whichline         - line number of the passed in scanline
     $field             - type of change to process       $field             - type of change to process 
                          (either                            (either 
                           'ID'     -> correct the student ID number                            'ID'     -> correct the student/employee ID
                           'CODE'   -> correct the CODE                            'CODE'   -> correct the CODE
                           'answer' -> fixup the submitted answers)                            'answer' -> fixup the submitted answers)
           
Line 5059  sub username_to_idmap { Line 6136  sub username_to_idmap {
                           - 'answer'                            - 'answer'
                                'response' - new answer or 'none' if blank                                 'response' - new answer or 'none' if blank
                                'question' - the bubble line to change                                 'question' - the bubble line to change
                                  'questionnum' - the question identifier,
                                                  may include subquestion. 
   
   Returns:    Returns:
     $line - the modified scanline      $line - the modified scanline
Line 5071  sub username_to_idmap { Line 6150  sub username_to_idmap {
   
 sub scantron_fixup_scanline {  sub scantron_fixup_scanline {
     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;      my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
   
     if ($field eq 'ID') {      if ($field eq 'ID') {
  if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {   if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
     return ($line,1,'New value too large');      return ($line,1,'New value too large');
Line 5108  sub scantron_fixup_scanline { Line 6186  sub scantron_fixup_scanline {
  my $answer=${off}x$length;   my $answer=${off}x$length;
  if ($args->{'response'} eq 'none') {   if ($args->{'response'} eq 'none') {
     &scan_data($scan_data,      &scan_data($scan_data,
        "$whichline.no_bubble.".$args->{'question'},'1');         "$whichline.no_bubble.".$args->{'questionnum'},'1');
  } else {   } else {
     if ($on eq 'letter') {      if ($on eq 'letter') {
  my @alphabet=('A'..'Z');   my @alphabet=('A'..'Z');
Line 5120  sub scantron_fixup_scanline { Line 6198  sub scantron_fixup_scanline {
  substr($answer,$args->{'response'},1)=$on;   substr($answer,$args->{'response'},1)=$on;
     }      }
     &scan_data($scan_data,      &scan_data($scan_data,
        "$whichline.no_bubble.".$args->{'question'},undef,'1');         "$whichline.no_bubble.".$args->{'questionnum'},undef,'1');
  }   }
  my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};   my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
  substr($line,$where-1,$length)=$answer;   substr($line,$where-1,$length)=$answer;
Line 5157  sub scan_data { Line 6235  sub scan_data {
     return $scan_data->{$filename.'_'.$key};      return $scan_data->{$filename.'_'.$key};
 }  }
   
   # ----- These first few routines are general use routines.----
   
   # Return the number of occurences of a pattern in a string.
   
   sub occurence_count {
       my ($string, $pattern) = @_;
   
       my @matches = ($string =~ /$pattern/g);
   
       return scalar(@matches);
   }
   
   
   # Take a string known to have digits and convert all the
   # digits into letters in the range J,A..I.
   
   sub digits_to_letters {
       my ($input) = @_;
   
       my @alphabet = ('J', 'A'..'I');
   
       my @input    = split(//, $input);
       my $output ='';
       for (my $i = 0; $i < scalar(@input); $i++) {
    if ($input[$i] =~ /\d/) {
       $output .= $alphabet[$input[$i]];
    } else {
       $output .= $input[$i];
    }
       }
       return $output;
   }
   
 =pod   =pod 
   
 =item scantron_parse_scanline  =item scantron_parse_scanline
   
   Decodes a scanline from the selected scantron file    Decodes a scanline from the selected bubblesheet file
   
  Arguments:   Arguments:
     line             - The text of the scantron file line to process      line             - The text of the bubblesheet file line to process
     whichline        - Line number      whichline        - Line number
     scantron_config  - Hash describing the format of the scantron lines.      scantron_config  - Hash describing the format of the bubblesheet lines.
     scan_data        - Hash of extra information about the scanline      scan_data        - Hash of extra information about the scanline
                        (see scantron_getfile for more information)                         (see scantron_getfile for more information)
     just_header      - True if should not process question answers but only      just_header      - True if should not process question answers but only
                        the stuff to the left of the answers.                         the stuff to the left of the answers.
       randomorder      - True if randomorder in use
       randompick       - True if randompick in use
       sequence         - Exam folder URL
       master_seq       - Ref to array containing symbs in exam folder
       symb_to_resource - Ref to hash of symbs for resources in exam folder
                          (corresponding values are resource objects)
       partids_by_symb  - Ref to hash of symb -> array ref of partIDs
       orderedforcode   - Ref to hash of arrays. keys are CODEs and values
                          are refs to an array of resource objects, ordered
                          according to order used for CODE, when randomorder
                          and or randompick are in use.
       respnumlookup    - Ref to hash mapping question numbers in bubble lines
                          for current line to question number used for same question
                           in "Master Sequence" (as seen by Course Coordinator).
       startline        - Ref to hash where key is question number (0 is first)
                          and value is number of first bubble line for current 
                          student or code-based randompick and/or randomorder.
       totalref         - Ref of scalar used to score total number of bubble
                          lines needed for responses in a scan line (used when
                          randompick in use. 
       
  Returns:   Returns:
    Hash containing the result of parsing the scanline     Hash containing the result of parsing the scanline
   
Line 5182  sub scan_data { Line 6314  sub scan_data {
        CODE_ignore_dup - 1 if the CODE is a duplicated use when unique         CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
                             CODEs were selected, but the usage has been                              CODEs were selected, but the usage has been
                             forced by the operator                              forced by the operator
        ID  - student ID         ID  - student/employee ID
        PaperID - if used, the ID number printed on the sheet when the          PaperID - if used, the ID number printed on the sheet when the 
                  paper was scanned                   paper was scanned
        FirstName - first name from the sheet         FirstName - first name from the sheet
Line 5215  sub scan_data { Line 6347  sub scan_data {
 =cut  =cut
   
 sub scantron_parse_scanline {  sub scantron_parse_scanline {
     my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;      my ($line,$whichline,$scantron_config,$scan_data,$just_header,$idmap,
           $randomorder,$randompick,$sequence,$master_seq,$symb_to_resource,
           $partids_by_symb,$orderedforcode,$respnumlookup,$startline,$totalref)=@_;
   
     my %record;      my %record;
     my $questions=substr($line,$$scantron_config{'Qstart'}-1);  # Answers      my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # stuff before answers
     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 5254  sub scantron_parse_scanline { Line 6388  sub scantron_parse_scanline {
     my $questnum=0;      my $questnum=0;
     my $ansnum  =1; # Multiple 'answer lines'/question.      my $ansnum  =1; # Multiple 'answer lines'/question.
   
     while ($questions) {      my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
  my $answers_needed = $bubble_lines_per_response{$questnum};      if ($randompick || $randomorder) {
  my $answer_length  = $$scantron_config{'Qlength'} * $answers_needed;          my $total = &get_respnum_lookups($sequence,$scan_data,$idmap,$line,\%record,
                                            $master_seq,$symb_to_resource,
                                            $partids_by_symb,$orderedforcode,
                                            $respnumlookup,$startline);
  $questnum++;          if ($total) {
  my $currentquest = substr($questions,0,$answer_length);              $lastpos = $total*$$scantron_config{'Qlength'}; 
  $questions       = substr($questions,0,$answer_length)='';          }
  if (length($currentquest) < $answer_length) { next; }          if (ref($totalref)) {
               $$totalref = $total;
  # 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      my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos);  # Answers
  #    bubbles.      chomp($questions); # Get rid of any trailing \n.
       $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
       while (length($questions)) {
  if ($$scantron_config{'Qon'} eq 'letter') {          my $answers_needed;
           if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
     if ($currentquest =~ /\?/              $answers_needed = $bubble_lines_per_response{$respnumlookup->{$questnum}};
  || $currentquest =~ /\*/          } else {
  || (&occurence_count($currentquest, "[A-Z]") > 1)) {      $answers_needed = $bubble_lines_per_response{$questnum};
  push(@{$record{'scantron.doubleerror'}},$questnum);          }
  for (my $ans = 0; $ans < $answers_needed; $ans++) {           my $answer_length  = ($$scantron_config{'Qlength'} * $answers_needed)
     my $bubble = substr($currentquest, $ans, 1);                               || 1;
     if ($bubble =~ /[A-Z]/ ) {          $questnum++;
  $record{"scantron.$ansnum.answer"} = $bubble;          my $quest_id = $questnum;
     } else {          my $currentquest = substr($questions,0,$answer_length);
  $record{"scantron.$ansnum.answer"}='';          $questions       = substr($questions,$answer_length);
     }          if (length($currentquest) < $answer_length) { next; }
     $ansnum++;  
  }          my $subdivided;
           if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
     } elsif (!defined($currentquest)              $subdivided = $subdivided_bubble_lines{$respnumlookup->{$questnum-1}};
      || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest))          } else {
      || (&occurence_count($currentquest, "[A-Z]") == 0)) {              $subdivided = $subdivided_bubble_lines{$questnum-1};
  for (my $ans = 0; $ans < $answers_needed; $ans++ ) {          }
     $record{"scantron.$ansnum.answer"}='';          if ($subdivided =~ /,/) {
     $ansnum++;              my $subquestnum = 1;
               my $subquestions = $currentquest;
  }              my @subanswers_needed = split(/,/,$subdivided);
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {              foreach my $subans (@subanswers_needed) {
     push(@{$record{"scantron.missingerror"}},$questnum);                  my $subans_length =
     $ansnum += $answers_needed;                      ($$scantron_config{'Qlength'} * $subans)  || 1;
  }                  my $currsubquest = substr($subquestions,0,$subans_length);
                   $subquestions   = substr($subquestions,$subans_length);
     } else {                  $quest_id = "$questnum.$subquestnum";
  for (my $ans = 0; $ans < $answers_needed; $ans++) {                  if (($$scantron_config{'Qon'} eq 'letter') ||
     $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);                      ($$scantron_config{'Qon'} eq 'number')) {
     $ansnum++;                      $ansnum = &scantron_validator_lettnum($ansnum, 
  }                          $questnum,$quest_id,$subans,$currsubquest,$whichline,
     }                          \@alphabet,\%record,$scantron_config,$scan_data,
                           $randomorder,$randompick,$respnumlookup);
                   } else {
                       $ansnum = &scantron_validator_positional($ansnum,
                           $questnum,$quest_id,$subans,$currsubquest,$whichline,
                           \@alphabet,\%record,$scantron_config,$scan_data,
                           $randomorder,$randompick,$respnumlookup);
                   }
                   $subquestnum ++;
               }
           } else {
               if (($$scantron_config{'Qon'} eq 'letter') ||
                   ($$scantron_config{'Qon'} eq 'number')) {
                   $ansnum = &scantron_validator_lettnum($ansnum,$questnum,
                       $quest_id,$answers_needed,$currentquest,$whichline,
                       \@alphabet,\%record,$scantron_config,$scan_data,
                       $randomorder,$randompick,$respnumlookup);
               } else {
                   $ansnum = &scantron_validator_positional($ansnum,$questnum,
                       $quest_id,$answers_needed,$currentquest,$whichline,
                       \@alphabet,\%record,$scantron_config,$scan_data,
                       $randomorder,$randompick,$respnumlookup);
               }
           }
       }
       $record{'scantron.maxquest'}=$questnum;
       return \%record;
   }
   
  # Qon 'number' implies each slot gives a digit that indexes the  sub get_master_seq {
  #    the bubbles filled or Qoff or a non number for unbubbled lines.      my ($resources,$master_seq,$symb_to_resource) = @_;
         #    and *? for double bubbles on a line.      return unless ((ref($resources) eq 'ARRAY') && (ref($master_seq) eq 'ARRAY') && 
  #    these answers are also stored as letters.                     (ref($symb_to_resource) eq 'HASH'));
       my $resource_error;
  } elsif ($$scantron_config{'Qon'} eq 'number') {      foreach my $resource (@{$resources}) {
     if ($currentquest =~ /\?/          my $ressymb;
  || $currentquest =~ /\*/          if (ref($resource)) {
  || (&occurence_count($currentquest, '\d') > 1)) {              $ressymb = $resource->symb();
  push(@{$record{'scantron.doubleerror'}},$questnum);              push(@{$master_seq},$ressymb);
  for (my $ans = 0; $ans < $answers_needed; $ans++) {              $symb_to_resource->{$ressymb} = $resource;
     my $bubble = substr($currentquest, $ans, 1);          } else {
     if ($bubble =~ /\d/) {              $resource_error = 1;
  $record{"scantron.$ansnum.answer"} = $alphabet[$bubble];              last;
     } else {          }
  $record{"scantron.$ansnum.answer"}=' ';      }
     }      return $resource_error;
     $ansnum++;  }
  }  
   
     } elsif (!defined($currentquest)  sub get_respnum_lookups {
      || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest))       my ($sequence,$scan_data,$idmap,$line,$record,$master_seq,$symb_to_resource,
      || (&occurence_count($currentquest, '\d') == 0)) {          $partids_by_symb,$orderedforcode,$respnumlookup,$startline) = @_;
  for (my $ans = 0; $ans < $answers_needed; $ans++ ) {      return unless ((ref($record) eq 'HASH') && (ref($master_seq) eq 'ARRAY') &&
     $record{"scantron.$ansnum.answer"}='';                     (ref($symb_to_resource) eq 'HASH') && (ref($partids_by_symb) eq 'HASH') &&
     $ansnum++;                     (ref($orderedforcode) eq 'HASH') && (ref($respnumlookup) eq 'HASH') &&
                      (ref($startline) eq 'HASH'));
       my ($user,$scancode);
       if ((exists($record->{'scantron.CODE'})) &&
           (&Apache::lonnet::validCODE($record->{'scantron.CODE'}))) {
           $scancode = $record->{'scantron.CODE'};
       } else {
           $user = &scantron_find_student($record,$scan_data,$idmap,$line);
       }
       my @mapresources =
           &users_order($user,$scancode,$sequence,$master_seq,$symb_to_resource,
                        $orderedforcode);
       my $total = 0;
       my $count = 0;
       foreach my $resource (@mapresources) {
           my $id = $resource->id();
           my $symb = $resource->symb();
           if (ref($partids_by_symb->{$symb}) eq 'ARRAY') {
               foreach my $partid (@{$partids_by_symb->{$symb}}) {
                   my $respnum = $masterseq_id_responsenum{$id.'_'.$partid};
                   if ($respnum ne '') {
                       $respnumlookup->{$count} = $respnum;
                       $startline->{$count} = $total;
                       $total += $bubble_lines_per_response{$respnum};
                       $count ++;
                   }
               }
           }
       }
       return $total;
   }
   
  }  sub scantron_validator_lettnum {
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {      my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
     push(@{$record{"scantron.missingerror"}},$questnum);          $alphabet,$record,$scantron_config,$scan_data,$randomorder,
     $ansnum += $answers_needed;          $randompick,$respnumlookup) = @_;
  }  
       # Qon 'letter' implies for each slot in currquest we have:
       #    ? or * for doubles, a letter in A-Z for a bubble, and
       #    about anything else (esp. a value of Qoff) for missing
       #    bubbles.
       #
       # Qon 'number' implies each slot gives a digit that indexes the
       #    bubbles filled, or Qoff, or a non-number for unbubbled lines,
       #    and * or ? for double bubbles on a single line.
       #
   
     } else {      my $matchon;
  $currentquest = &digits_to_letters($currentquest);      if ($$scantron_config{'Qon'} eq 'letter') {
  for (my $ans =0; $ans < $answers_needed; $ans++) {          $matchon = '[A-Z]';
     $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);      } elsif ($$scantron_config{'Qon'} eq 'number') {
     $ansnum++;          $matchon = '\d';
  }      }
     }      my $occurrences = 0;
  } else {      my $responsenum = $questnum-1;
       if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
          $responsenum = $respnumlookup->{$questnum-1} 
       }
       if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
           ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
           ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
           ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
           ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
           ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
           my @singlelines = split('',$currquest);
           foreach my $entry (@singlelines) {
               $occurrences = &occurence_count($entry,$matchon);
               if ($occurrences > 1) {
                   last;
               }
           }
       } else {
           $occurrences = &occurence_count($currquest,$matchon); 
       }
       if (($currquest =~ /\?/ || $currquest =~ /\*/) || ($occurrences > 1)) {
           push(@{$record->{'scantron.doubleerror'}},$quest_id);
           for (my $ans=0; $ans<$answers_needed; $ans++) {
               my $bubble = substr($currquest,$ans,1);
               if ($bubble =~ /$matchon/ ) {
                   if ($$scantron_config{'Qon'} eq 'number') {
                       if ($bubble == 0) {
                           $bubble = 10; 
                       }
                       $record->{"scantron.$ansnum.answer"} = 
                           $alphabet->[$bubble-1];
                   } else {
                       $record->{"scantron.$ansnum.answer"} = $bubble;
                   }
               } else {
                   $record->{"scantron.$ansnum.answer"}='';
               }
               $ansnum++;
           }
       } elsif (!defined($currquest)
               || (&occurence_count($currquest, $$scantron_config{'Qoff'}) == length($currquest))
               || (&occurence_count($currquest,$matchon) == 0)) {
           for (my $ans=0; $ans<$answers_needed; $ans++ ) {
               $record->{"scantron.$ansnum.answer"}='';
               $ansnum++;
           }
           if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
               push(@{$record->{'scantron.missingerror'}},$quest_id);
           }
       } else {
           if ($$scantron_config{'Qon'} eq 'number') {
               $currquest = &digits_to_letters($currquest);            
           }
           for (my $ans=0; $ans<$answers_needed; $ans++) {
               my $bubble = substr($currquest,$ans,1);
               $record->{"scantron.$ansnum.answer"} = $bubble;
               $ansnum++;
           }
       }
       return $ansnum;
   }
   
     # Otherwise there's a positional notation;  sub scantron_validator_positional {
     # each bubble line requires Qlength items, and there are filled in      my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
     # bubbles for each case where there 'Qon' characters.          $whichline,$alphabet,$record,$scantron_config,$scan_data,
     #          $randomorder,$randompick,$respnumlookup) = @_;
   
     my @array=split($$scantron_config{'Qon'},$currentquest,-1);      # 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.
       #
   
     # If the split only  giveas us one element.. the full length of the      my @array=split($$scantron_config{'Qon'},$currquest,-1);
     # answser string, no bubbles are filled in:  
   
     if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {      # If the split only gives us one element.. the full length of the
  for (my $ans = 0; $ans < $answers_needed; $ans++ ) {      # answer string, no bubbles are filled in:
     $record{"scantron.$ansnum.answer"}='';  
     $ansnum++;  
   
  }      if ($answers_needed eq '') {
  if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {          return;
     push(@{$record{"scantron.missingerror"}},$questnum);      }
  }  
     } elsif (scalar(@array) lt 2) {  
   
  my $location      = length($array[0]);      if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
  my $line_num      = $location / $$scantron_config{'Qlength'};          for (my $ans=0; $ans<$answers_needed; $ans++ ) {
  my $bubble        = $alphabet[$location % $$scantron_config{'Qlength'}];              $record->{"scantron.$ansnum.answer"}='';
               $ansnum++;
           }
           if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
               push(@{$record->{"scantron.missingerror"}},$quest_id);
           }
       } elsif (scalar(@array) == 2) {
           my $location = length($array[0]);
           my $line_num = int($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++;
            }
       } else {
           #  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.
           #
           my $responsenum = $questnum-1;
           if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
               $responsenum = $respnumlookup->{$questnum-1}
           }
           if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
               ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
               ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
               ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
               ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
               ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
               my $doubleerror = 0;
               while (($currquest >= $$scantron_config{'Qlength'}) && 
                      (!$doubleerror)) {
                  my $currline = substr($currquest,0,$$scantron_config{'Qlength'});
                  $currquest = substr($currquest,$$scantron_config{'Qlength'});
                  my @currarray = split($$scantron_config{'Qon'},$currline,-1);
                  if (length(@currarray) > 2) {
                      $doubleerror = 1;
                  } 
               }
               if ($doubleerror) {
                   push(@{$record->{'scantron.doubleerror'}},$quest_id);
               }
           } else {
               push(@{$record->{'scantron.doubleerror'}},$quest_id);
           }
           my $item = $ansnum;
           for (my $ans=0; $ans<$answers_needed; $ans++) {
               $record->{"scantron.$item.answer"} = '';
               $item ++;
           }
   
  for (my $ans = 0; $ans < $answers_needed; $ans++) {          my @ans=@array;
     if ($ans eq $line_num) {          my $i=0;
  $record{"scantron.$ansnum.answer"} = $bubble;          my $increment = 0;
     } else {          while ($#ans) {
  $record{"scantron.$ansnum.answer"} = ' ';              $i+=length($ans[0]) + $increment;
     }              my $line   = int($i/$$scantron_config{'Qlength'} + $ansnum);
     $ansnum++;              my $bubble = $i%$$scantron_config{'Qlength'};
  }              $record->{"scantron.$line.answer"}.=$alphabet->[$bubble];
     }              shift(@ans);
     #  If there's more than one instance of a bubble character              $increment = 1;
     #  That's a double bubble; with positional notation we can          }
     #  record all the bubbles filled in as well as the           $ansnum += $answers_needed;
     #  fact this response consists of multiple bubbles.  
     #  
     else {  
  push(@{$record{'scantron.doubleerror'}},$questnum);  
   
  my $first_answer = $ansnum;  
  for (my $ans =0; $ans < $answers_needed; $ans++) {  
     my $item = $first_answer+$ans;  
     $record{"scantron.$item.answer"} = '';  
  }  
   
  my @ans=@array;  
  my $i=0;  
  my $increment = 0;  
  while ($#ans) {  
     $i+=length($ans[0]) + $increment;  
     my $line   = int($i/$$scantron_config{'Qlength'} + $first_answer);  
     my $bubble = $i%$$scantron_config{'Qlength'};  
     $record{"scantron.$line.answer"}.=$alphabet[$bubble];  
     shift(@ans);  
     $increment = 1;  
  }  
  $ansnum += $answers_needed;  
     }  
  }  
     }      }
     $record{'scantron.maxquest'}=$questnum;      return $ansnum;
     return \%record;  
 }  }
   
 =pod  =pod
Line 5510  sub scantron_filter { Line 6795  sub scantron_filter {
   
 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=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
     my $classlist=&Apache::loncoursedata::get_classlist();      my $classlist=&Apache::loncoursedata::get_classlist();
     my $which=$env{'form.scantron_line'};      my $which=$env{'form.scantron_line'};
Line 5553  sub scantron_process_corrections { Line 6838  sub scantron_process_corrections {
  &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,   &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
  $which,'answer',   $which,'answer',
  { 'question'=>$question,   { 'question'=>$question,
        'response'=>$env{"form.scantron_correct_Q_$question"}});           'response'=>$env{"form.scantron_correct_Q_$question"},
                                      'questionnum'=>$env{"form.scantron_questionnum_Q_$question"}});
     if ($err) { last; }      if ($err) { last; }
  }   }
     }      }
     if ($err) {      if ($err) {
  $r->print("<span class=\"LC_warning\">Unable to accept last correction, an error occurred :$errmsg:</span>");          $r->print(
               '<p class="LC_error">'
              .&mt('Unable to accept last correction, an error occurred: [_1]',
                   $errmsg)
              .'</p>');
     } else {      } else {
  &scantron_put_line($scanlines,$scan_data,$which,$line,$skip);   &scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
  &scantron_putfile($scanlines,$scan_data);   &scantron_putfile($scanlines,$scan_data);
Line 5650  sub remember_current_skipped { Line 6940  sub remember_current_skipped {
 =item check_for_error  =item check_for_error
   
     Checks if there was an error when attempting to remove a specific      Checks if there was an error when attempting to remove a specific
     scantron_.. bubble sheet data file. Prints out an error if      scantron_.. bubblesheet data file. Prints out an error if
     something went wrong.      something went wrong.
   
 =cut  =cut
Line 5658  sub remember_current_skipped { Line 6948  sub remember_current_skipped {
 sub check_for_error {  sub check_for_error {
     my ($r,$result)=@_;      my ($r,$result)=@_;
     if ($result ne 'ok' && $result ne 'not_found' ) {      if ($result ne 'ok' && $result ne 'not_found' ) {
  $r->print("An error occurred ($result) when trying to Remove the existing corrections.");   $r->print(&mt("An error occurred ([_1]) when trying to remove the existing corrections.",$result));
     }      }
 }  }
   
Line 5672  sub check_for_error { Line 6962  sub check_for_error {
 =cut  =cut
   
 sub scantron_warning_screen {  sub scantron_warning_screen {
     my ($button_text)=@_;      my ($button_text,$symb)=@_;
     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});      my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my $CODElist;      my $CODElist;
     if ($scantron_config{'CODElocation'} &&      if ($scantron_config{'CODElocation'} &&
  $scantron_config{'CODEstart'} &&   $scantron_config{'CODEstart'} &&
  $scantron_config{'CODElength'}) {   $scantron_config{'CODElength'}) {
  $CODElist=$env{'form.scantron_CODElist'};   $CODElist=$env{'form.scantron_CODElist'};
  if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">None</span>'; }   if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">'.&mt('None').'</span>'; }
  $CODElist=   $CODElist=
     '<tr><td><b>List of CODES to validate against:</b></td><td><tt>'.      '<tr><td><b>'.&mt('List of CODES to validate against:').'</b></td><td><tt>'.
     $env{'form.scantron_CODElist'}.'</tt></td></tr>';      $env{'form.scantron_CODElist'}.'</tt></td></tr>';
     }      }
     return (<<STUFF);      my $lastbubblepoints;
       if ($env{'form.scantron_lastbubblepoints'} ne '') {
           $lastbubblepoints =
               '<tr><td><b>'.&mt('Hand-graded items: points from last bubble in row').'</b></td><td><tt>'.
               $env{'form.scantron_lastbubblepoints'}.'</tt></td></tr>';
       }
       return ('
 <p>  <p>
 <span class="LC_warning">Please double check the information  <span class="LC_warning">
                  below before clicking on '$button_text'</span>  '.&mt("Please double check the information below before clicking on '[_1]'",&mt($button_text)).'</span>
 </p>  </p>
 <table>  <table>
 <tr><td><b>Sequence to be Graded:</b></td><td>$title</td></tr>  <tr><td><b>'.&mt('Sequence to be Graded:').'</b></td><td>'.$title.'</td></tr>
 <tr><td><b>Data File that will be used:</b></td><td><tt>$env{'form.scantron_selectfile'}</tt></td></tr>  <tr><td><b>'.&mt('Data File that will be used:').'</b></td><td><tt>'.$env{'form.scantron_selectfile'}.'</tt></td></tr>
 $CODElist  '.$CODElist.$lastbubblepoints.'
 </table>  </table>
 <br />  <p> '.&mt("If this information is correct, please click on '[_1]'.",&mt($button_text)).'<br />
 <p> If this information is correct, please click on '$button_text'.</p>  '.&mt('If something is incorrect, please return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','<a href="/adm/grades?symb='.$symb.'&command=scantron_selectphase" class="LC_info">','</a>').'</p>
 <p> If something is incorrect, please click the 'Grading Menu' button to start over.</p>  
   
 <br />  <br />
 STUFF  ');
 }  }
   
 =pod  =pod
Line 5713  STUFF Line 7008  STUFF
 =cut  =cut
   
 sub scantron_do_warning {  sub scantron_do_warning {
     my ($r)=@_;      my ($r,$symb)=@_;
     my ($symb)=&get_symb($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $default_form_data=&defaultFormData($symb);      my $default_form_data=&defaultFormData($symb);
     $r->print(&scantron_form_start().$default_form_data);      $r->print(&scantron_form_start().$default_form_data);
     if ( $env{'form.selectpage'} eq '' ||      if ( $env{'form.selectpage'} eq '' ||
  $env{'form.scantron_selectfile'} eq '' ||   $env{'form.scantron_selectfile'} eq '' ||
  $env{'form.scantron_format'} eq '' ) {   $env{'form.scantron_format'} eq '' ) {
  $r->print("<p>You have forgetten to specify some information. Please go Back and try again.</p>");   $r->print("<p>".&mt('You have forgotten to specify some information. Please go Back and try again.')."</p>");
  if ( $env{'form.selectpage'} eq '') {   if ( $env{'form.selectpage'} eq '') {
     $r->print('<p><span class="LC_error">You have not selected a Sequence to grade</span></p>');      $r->print('<p><span class="LC_error">'.&mt('You have not selected a Sequence to grade').'</span></p>');
  }    } 
  if ( $env{'form.scantron_selectfile'} eq '') {   if ( $env{'form.scantron_selectfile'} eq '') {
     $r->print('<p><span class="LC_error">You have not selected a file that contains the student\'s response data.</span></p>');      $r->print('<p><span class="LC_error">'.&mt("You have not selected a file that contains the student's response data.").'</span></p>');
  }    } 
  if ( $env{'form.scantron_format'} eq '') {   if ( $env{'form.scantron_format'} eq '') {
     $r->print('<p><span class="LC_error">You have not selected a the format of the student\'s response data.</span></p>');      $r->print('<p><span class="LC_error">'.&mt("You have not selected the format of the student's response data.").'</span></p>');
  }    } 
     } else {      } else {
  my $warning=&scantron_warning_screen('Grading: Validate Records');   my $warning=&scantron_warning_screen('Grading: Validate Records',$symb);
  $r->print(<<STUFF);          my $bubbledbyhand=&hand_bubble_option();
 $warning   $r->print('
 <input type="submit" name="submit" value="Grading: Validate Records" />  '.$warning.$bubbledbyhand.'
   <input type="submit" name="submit" value="'.&mt('Grading: Validate Records').'" />
 <input type="hidden" name="command" value="scantron_validate" />  <input type="hidden" name="command" value="scantron_validate" />
 STUFF  ');
     }      }
     $r->print("</form><br />".&show_grading_menu_form($symb));      $r->print("</form><br />");
     return '';      return '';
 }  }
   
Line 5772  SCANTRONFORM Line 7067  SCANTRONFORM
    '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";     '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
        $chunk .=         $chunk .=
    '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";     '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
          $chunk .= 
              '<input type="hidden" name="scantron.sub_bubblelines.'.$line.'" value="'.$env{"form.scantron.sub_bubblelines.$line"}.'" />'."\n";
          $chunk .=
              '<input type="hidden" name="scantron.responsetype.'.$line.'" value="'.$env{"form.scantron.responsetype.$line"}.'" />'."\n";
          $chunk .=
              '<input type="hidden" name="scantron.residpart.'.$line.'" value="'.$env{"form.scantron.residpart.$line"}.'" />'."\n";
        $result .= $chunk;         $result .= $chunk;
        $line++;         $line++;
    }      }
     return $result;      return $result;
 }  }
   
Line 5782  SCANTRONFORM Line 7083  SCANTRONFORM
   
 =item scantron_validate_file  =item scantron_validate_file
   
     Dispatch routine for doing validation of a bubble sheet data file.      Dispatch routine for doing validation of a bubblesheet data file.
   
     Also processes any necessary information resets that need to      Also processes any necessary information resets that need to
     occur before validation begins (ignore previous corrections,      occur before validation begins (ignore previous corrections,
Line 5791  SCANTRONFORM Line 7092  SCANTRONFORM
 =cut  =cut
   
 sub scantron_validate_file {  sub scantron_validate_file {
     my ($r) = @_;      my ($r,$symb) = @_;
     my ($symb)=&get_symb($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     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 before 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 5816  sub scantron_validate_file { Line 7116  sub scantron_validate_file {
     if ($env{'form.scantron_corrections'}) {      if ($env{'form.scantron_corrections'}) {
  &scantron_process_corrections($r);   &scantron_process_corrections($r);
     }      }
     $r->print("<p>Gathering necessary info.</p>");$r->rflush();      $r->print('<p>'.&mt('Gathering necessary information.').'</p>');$r->rflush();
     #get the student pick code ready      #get the student pick code ready
     $r->print(&Apache::loncommon::studentbrowser_javascript());      $r->print(&Apache::loncommon::studentbrowser_javascript());
     my $max_bubble=&scantron_get_maxbubble();      my $nav_error;
       my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
       my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
       if ($nav_error) {
           $r->print(&navmap_errormsg());
           return '';
       }
     my $result=&scantron_form_start($max_bubble).$default_form_data;      my $result=&scantron_form_start($max_bubble).$default_form_data;
       if ($env{'form.scantron_lastbubblepoints'} ne '') {
           $result .= '<input type="hidden" name="scantron_lastbubblepoints" value="'.$env{'form.scantron_lastbubblepoints'}.'" />';
       }
     $r->print($result);      $r->print($result);
           
     my @validate_phases=( 'sequence',      my @validate_phases=( 'sequence',
Line 5836  sub scantron_validate_file { Line 7145  sub scantron_validate_file {
   
     my $stop=0;      my $stop=0;
     while (!$stop && $currentphase < scalar(@validate_phases)) {      while (!$stop && $currentphase < scalar(@validate_phases)) {
  $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");   $r->print(&mt('Validating '.$validate_phases[$currentphase]).'<br />');
  $r->rflush();   $r->rflush();
        
  my $which="scantron_validate_".$validate_phases[$currentphase];   my $which="scantron_validate_".$validate_phases[$currentphase];
  {   {
     no strict 'refs';      no strict 'refs';
Line 5845  sub scantron_validate_file { Line 7155  sub scantron_validate_file {
  }   }
     }      }
     if (!$stop) {      if (!$stop) {
  my $warning=&scantron_warning_screen('Start Grading');   my $warning=&scantron_warning_screen('Start Grading',$symb);
  $r->print(<<STUFF);   $r->print(&mt('Validation process complete.').'<br />'.
 Validation process complete.<br />                    $warning.
 $warning                    &mt('Perform verification for each student after storage of submissions?').
 <input type="submit" name="submit" value="Start Grading" />                    '&nbsp;<span class="LC_nobreak"><label>'.
 <input type="hidden" name="command" value="scantron_process" />                    '<input type="radio" name="verifyrecord" value="1" />'.&mt('Yes').'</label>'.
 STUFF                    ('&nbsp;'x3).'<label>'.
                     '<input type="radio" name="verifyrecord" value="0" checked="checked" />'.&mt('No').
                     '</label></span><br />'.
                     &mt('Grading will take longer if you use verification.').'<br />'.
                     &mt('Otherwise, Grade/Manage/Review Bubblesheets [_1] Review bubblesheet data can be used once grading is complete.','&raquo;').'<br /><br />'.
                     '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'.
                     '<input type="hidden" name="command" value="scantron_process" />'."\n");
     } else {      } else {
  $r->print('<input type="hidden" name="command" value="scantron_validate" />');   $r->print('<input type="hidden" name="command" value="scantron_validate" />');
  $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");   $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
     }      }
     if ($stop) {      if ($stop) {
  if ($validate_phases[$currentphase] eq 'sequence') {   if ($validate_phases[$currentphase] eq 'sequence') {
     $r->print('<input type="submit" name="submit" value="Ignore -> " />');      $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' &rarr; " />');
     $r->print(' this error <br />');      $r->print(' '.&mt('this error').' <br />');
   
     $r->print(" <p>Or click the 'Grading Menu' button to start over.</p>");      $r->print('<p>'.&mt('Or return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','<a href="/adm/grades?symb='.$symb.'&command=scantron_selectphase" class="LC_info">','</a>').'</p>');
  } else {   } else {
     $r->print('<input type="submit" name="submit" value="Continue ->" />');              if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
     $r->print(' using corrected info <br />');          $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' &rarr;" onclick="javascript:verify_bubble_radio(this.form)" />');
     $r->print("<input type='submit' value='Skip' name='scantron_skip_record' />");              } else {
     $r->print(" this scanline saving it for later.");                  $r->print('<input type="submit" name="submit" value="'.&mt('Continue').' &rarr;" />');
               }
       $r->print(' '.&mt('using corrected info').' <br />');
       $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />");
       $r->print(" ".&mt("this scanline saving it for later."));
  }   }
     }      }
     $r->print(" </form><br />".&show_grading_menu_form($symb));      $r->print(" </form><br />");
     return '';      return '';
 }  }
   
Line 5879  STUFF Line 7198  STUFF
   
 =item scantron_remove_file  =item scantron_remove_file
   
    Removes the requested bubble sheet data file, makes sure that     Removes the requested bubblesheet data file, makes sure that
    scantron_original_<filename> is never removed     scantron_original_<filename> is never removed
   
   
Line 5904  sub scantron_remove_file { Line 7223  sub scantron_remove_file {
   
 =item scantron_remove_scan_data  =item scantron_remove_scan_data
   
    Removes all scan_data correction for the requested bubble sheet     Removes all scan_data correction for the requested bubblesheet
    data file.  (In the case that both the are doing skipped records we need     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     to remember the old skipped lines for the time being so that element
    persists for a while.)     persists for a while.)
Line 5928  sub scantron_remove_scan_data { Line 7247  sub scantron_remove_scan_data {
     }      }
     my $result;      my $result;
     if (@todelete) {      if (@todelete) {
  $result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname);   $result = &Apache::lonnet::del('nohist_scantrondata',
          \@todelete,$cdom,$cname);
       } else {
    $result = 'ok';
     }      }
     return $result;      return $result;
 }  }
Line 5938  sub scantron_remove_scan_data { Line 7260  sub scantron_remove_scan_data {
   
 =item scantron_getfile  =item scantron_getfile
   
     Fetches the requested bubble sheet data file (all 3 versions), and      Fetches the requested bubblesheet data file (all 3 versions), and
     the scan_data hash      the scan_data hash
       
   Arguments:    Arguments:
Line 6038  sub lonnet_putfile { Line 7360  sub lonnet_putfile {
   
 =item scantron_putfile  =item scantron_putfile
   
     Stores the current version of the bubble sheet data files, and the      Stores the current version of the bubblesheet data files, and the
     scan_data hash. (Does not modify the original version only the      scan_data hash. (Does not modify the original version only the
     corrected and skipped versions.      corrected and skipped versions.
   
Line 6132  sub get_todo_count { Line 7454  sub get_todo_count {
   
 =item scantron_put_line  =item scantron_put_line
   
     Updates the 'corrected' or 'skipped' versions of the bubble sheet      Updates the 'corrected' or 'skipped' versions of the bubblesheet
     data file.      data file.
   
  Arguments:   Arguments:
Line 6219  sub scantron_validate_sequence { Line 7541  sub scantron_validate_sequence {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
   
     my $navmap=Apache::lonnavmaps::navmap->new();      my $navmap=Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $r->print(&navmap_errormsg());
           return (1,$currentphase);
       }
     my (undef,undef,$sequence)=      my (undef,undef,$sequence)=
  &Apache::lonnet::decode_symb($env{'form.selectpage'});   &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
Line 6230  sub scantron_validate_sequence { Line 7556  sub scantron_validate_sequence {
  my @resources=   my @resources=
     $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);      $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
  if (@resources) {   if (@resources) {
     $r->print("<p>".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."</p>");      $r->print(
                   '<p class="LC_warning">'
                  .&mt('Some resources in the sequence currently are not set to'
                      .' bubblesheet exam mode. Grading these resources currently may not'
                      .' work correctly.')
                  .'</p>'
               );
     return (1,$currentphase);      return (1,$currentphase);
  }   }
     }      }
Line 6238  sub scantron_validate_sequence { Line 7570  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 6255  sub scantron_validate_ID { Line 7580  sub scantron_validate_ID {
     my %idmap=&username_to_idmap($classlist);      my %idmap=&username_to_idmap($classlist);
   
     #get scantron line setup      #get scantron line setup
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&Apache::lonnet::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 $nav_error;
       &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble_lines.. array.
       if ($nav_error) {
           $r->print(&navmap_errormsg());
           return(1,$currentphase);
       }
   
     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 6311  sub scantron_validate_ID { Line 7641  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,
           $randomorder,$randompick,$respnumlookup,$startline)=@_;
 #FIXME in the case of a duplicated ID the previous line, probably need  #FIXME in the case of a duplicated ID the previous line, probably need
 #to show both the current line and the previous one and allow skipping  #to show both the current line and the previous one and allow skipping
 #the previous one or the current one  #the previous one or the current one
   
     $r->print("<p><b>An error was detected ($error)</b>");  
     if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {      if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
  $r->print(" for PaperID <tt>".          $r->print(
   $$scan_record{'scantron.PaperID'}."</tt> \n");              '<p class="LC_warning">'
              .&mt('An error was detected ([_1]) for PaperID [_2]',
                   "<b>$error</b>",
                   '<tt>'.$$scan_record{'scantron.PaperID'}.'</tt>')
              ."</p> \n");
     } else {      } else {
  $r->print(" in scanline $i <pre>".          $r->print(
   $line."</pre> \n");              '<p class="LC_warning">'
     }             .&mt('An error was detected ([_1]) in scanline [_2] [_3]',
     my $message="<p>The ID on the form is  <tt>".                  "<b>$error</b>", $i, "<pre>$line</pre>")
  $$scan_record{'scantron.ID'}."</tt><br />\n".             ."</p> \n");
  "The name on the paper is ".      }
  $$scan_record{'scantron.LastName'}.",".      my $message =
  $$scan_record{'scantron.FirstName'}."</p>";          '<p>'
          .&mt('The ID on the form is [_1]',
               "<tt>$$scan_record{'scantron.ID'}</tt>")
          .'<br />'
          .&mt('The name on the paper is [_1], [_2]',
               $$scan_record{'scantron.LastName'},
               $$scan_record{'scantron.FirstName'})
          .'</p>';
   
     $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");      $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");      $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
                              # Array populated for doublebubble or
       my @lines_to_correct;  # missingbubble errors to build javascript
                              # to validate radio button checking   
   
     if ($error =~ /ID$/) {      if ($error =~ /ID$/) {
  if ($error eq 'incorrectID') {   if ($error eq 'incorrectID') {
     $r->print("The encoded ID is not in the classlist</p>\n");              $r->print('<p class="LC_warning">'.&mt("The encoded ID is not in the classlist").
         "</p>\n");
  } elsif ($error eq 'duplicateID') {   } elsif ($error eq 'duplicateID') {
     $r->print("The encoded ID has also been used by a previous paper $arg</p>\n");              $r->print('<p class="LC_warning">'.&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."</p>\n");
  }   }
  $r->print($message);   $r->print($message);
  $r->print("<p>How should I handle this? <br /> \n");   $r->print("<p>".&mt("How should I handle this?")." <br /> \n");
  $r->print("\n<ul><li> ");   $r->print("\n<ul><li> ");
  #FIXME it would be nice if this sent back the user ID and   #FIXME it would be nice if this sent back the user ID and
  #could do partial userID matches   #could do partial userID matches
  $r->print(&Apache::loncommon::selectstudent_link('scantronupload',   $r->print(&Apache::loncommon::selectstudent_link('scantronupload',
        'scantron_username','scantron_domain'));         'scantron_username','scantron_domain'));
  $r->print(": <input type='text' name='scantron_username' value='' />");   $r->print(": <input type='text' name='scantron_username' value='' />");
  $r->print("\n@".   $r->print("\n:\n".
  &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));   &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
   
  $r->print('</li>');   $r->print('</li>');
     } elsif ($error =~ /CODE$/) {      } elsif ($error =~ /CODE$/) {
  if ($error eq 'incorrectCODE') {   if ($error eq 'incorrectCODE') {
     $r->print("</p><p>The encoded CODE is not in the list of possible CODEs</p>\n");      $r->print('<p class="LC_warning">'.&mt("The encoded CODE is not in the list of possible CODEs.")."</p>\n");
  } elsif ($error eq 'duplicateCODE') {   } elsif ($error eq 'duplicateCODE') {
     $r->print("</p><p>The encoded CODE has also been used by a previous paper ".join(', ',@{$arg}).", and CODEs are supposed to be unique</p>\n");      $r->print('<p class="LC_warning">'.&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."</p>\n");
  }   }
  $r->print("<p>The CODE on the form is  <tt>'".   $r->print("<p>".&mt('The CODE on the form is [_1]',
   $$scan_record{'scantron.CODE'}."'</tt><br />\n");      "<tt>'$$scan_record{'scantron.CODE'}'</tt>")
                    ."</p>\n");
  $r->print($message);   $r->print($message);
  $r->print("<p>How should I handle this? <br /> \n");   $r->print("<p>".&mt("How should I handle this?")."</p>\n");
  $r->print("\n<br /> ");   $r->print("\n<br /> ");
  my $i=0;   my $i=0;
  if ($error eq 'incorrectCODE'    if ($error eq 'incorrectCODE' 
Line 6400  sub scantron_get_correction { Line 7717  sub scantron_get_correction {
     if ($closest > 0) {      if ($closest > 0) {
  foreach my $testcode (@{$closest}) {   foreach my $testcode (@{$closest}) {
     my $checked='';      my $checked='';
     if (!$i) { $checked=' checked="checked" '; }      if (!$i) { $checked=' checked="checked"'; }
     $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.</label><input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");      $r->print("
      <label>
          <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i'$checked />
          ".&mt("Use the similar CODE [_1] instead.",
       "<b><tt>".$testcode."</tt></b>")."
       </label>
       <input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
     $r->print("\n<br />");      $r->print("\n<br />");
     $i++;      $i++;
  }   }
     }      }
  }   }
  if ($$scan_record{'scantron.CODE'}=~/\S/ ) {   if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
     my $checked; if (!$i) { $checked=' checked="checked" '; }      my $checked; if (!$i) { $checked=' checked="checked"'; }
     $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.</label>");      $r->print("
       <label>
           <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked />
          ".&mt("Use the CODE [_1] that was on the paper, ignoring the error.",
        "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
       </label>");
     $r->print("\n<br />");      $r->print("\n<br />");
  }   }
   
  $r->print(<<ENDSCRIPT);   $r->print(&Apache::lonhtmlcommon::scripttag(<<ENDSCRIPT));
 <script type="text/javascript">  
 function change_radio(field) {  function change_radio(field) {
     var slct=document.scantronupload.scantron_CODE_resolution;      var slct=document.scantronupload.scantron_CODE_resolution;
     var i;      var i;
Line 6422  function change_radio(field) { Line 7749  function change_radio(field) {
         if (slct[i].value==field) { slct[i].checked=true; }          if (slct[i].value==field) { slct[i].checked=true; }
     }      }
 }  }
 </script>  
 ENDSCRIPT  ENDSCRIPT
  my $href="/adm/pickcode?".   my $href="/adm/pickcode?".
    "form=".&escape("scantronupload").     "form=".&escape("scantronupload").
Line 6431  ENDSCRIPT Line 7757  ENDSCRIPT
    "&curCODE=".&escape($$scan_record{'scantron.CODE'}).     "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
    "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});     "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
  if ($env{'form.scantron_CODElist'} =~ /\S/) {    if ($env{'form.scantron_CODElist'} =~ /\S/) { 
     $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_found' /> <a target='_blank' href='$href'>Select</a> a CODE from the list of all CODEs and use it.</label> Selected CODE is <input readonly='true' type='text' size='8' name='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />");      $r->print("
       <label>
          <input type='radio' name='scantron_CODE_resolution' value='use_found' />
          ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
        "<a target='_blank' href='$href'>","</a>")."
       </label> 
       ".&mt("Selected CODE is [_1]",'<input readonly="readonly" type="text" size="8" name="scantron_CODE_selectedvalue" onfocus="javascript:change_radio(\'use_found\')" onchange="javascript:change_radio(\'use_found\')" />'));
     $r->print("\n<br />");      $r->print("\n<br />");
  }   }
  $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_typed' /> Use </label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" /> as the CODE.");   $r->print("
       <label>
          <input type='radio' name='scantron_CODE_resolution' value='use_typed' />
          ".&mt("Use [_1] as the CODE.",
        "</label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" />"));
  $r->print("\n<br /><br />");   $r->print("\n<br /><br />");
     } elsif ($error eq 'doublebubble') {      } elsif ($error eq 'doublebubble') {
  $r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n");   $r->print('<p class="LC_warning">'.&mt("There have been multiple bubbles scanned for some question(s)")."</p>\n");
   
    # The form field scantron_questions is acutally a list of line numbers.
    # represented by this form so:
   
    my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
                                                   $respnumlookup,$startline);
   
  $r->print('<input type="hidden" name="scantron_questions" value="'.   $r->print('<input type="hidden" name="scantron_questions" value="'.
   join(',',@{$arg}).'" />');    $line_list.'" />');
  $r->print($message);   $r->print($message);
  $r->print("<p>Please indicate which bubble should be used for grading</p>");   $r->print("<p>".&mt("Please indicate which bubble should be used for grading")."</p>");
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my $selected  = &get_response_bubbles($scan_record, $question);      my @linenums = &prompt_for_corrections($r,$question,$scan_config,
     my @select_array = split(/:/,$selected);                                                     $scan_record, $error,
     &scantron_bubble_selector($r,$scan_config,$question,                                                     $randomorder,$randompick,
       @select_array);                                                     $respnumlookup,$startline);
               push(@lines_to_correct,@linenums);
  }   }
           $r->print(&verify_bubbles_checked(@lines_to_correct));
     } elsif ($error eq 'missingbubble') {      } elsif ($error eq 'missingbubble') {
  $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");   $r->print('<p class="LC_warning">'.&mt("There have been [_1]no[_2] bubbles scanned for some question(s)",'<b>','</b>')."</p>\n");
  $r->print($message);   $r->print($message);
  $r->print("<p>Please indicate which bubble should be used for grading</p>");   $r->print("<p>".&mt("Please indicate which bubble should be used for grading.")."</p>");
  $r->print("Some questions have no scanned bubbles\n");   $r->print(&mt("Some questions have no scanned bubbles.")."\n");
   
    # The form field scantron_questions is actually a list of line numbers not
    # a list of question numbers. Therefore:
    #
   
    my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
                                                   $respnumlookup,$startline);
   
  $r->print('<input type="hidden" name="scantron_questions" value="'.   $r->print('<input type="hidden" name="scantron_questions" value="'.
   join(',',@{$arg}).'" />');    $line_list.'" />');
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my $selected = &get_response_bubbles($scan_record, $question);      my @linenums = &prompt_for_corrections($r,$question,$scan_config,
     &scantron_bubble_selector($r,$scan_config,$question);                                                     $scan_record, $error,
                                                      $randomorder,$randompick,
                                                      $respnumlookup,$startline);
               push(@lines_to_correct,@linenums);
  }   }
           $r->print(&verify_bubbles_checked(@lines_to_correct));
     } else {      } else {
  $r->print("\n<ul>");   $r->print("\n<ul>");
     }      }
     $r->print("\n</li></ul>");      $r->print("\n</li></ul>");
   }
   
   sub verify_bubbles_checked {
       my (@ansnums) = @_;
       my $ansnumstr = join('","',@ansnums);
       my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
       &js_escape(\$warning);
       my $output = &Apache::lonhtmlcommon::scripttag((<<ENDSCRIPT));
   function verify_bubble_radio(form) {
       var ansnumArray = new Array ("$ansnumstr");
       var need_bubble_count = 0;
       for (var i=0; i<ansnumArray.length; i++) {
           if (form.elements["scantron_correct_Q_"+ansnumArray[i]].length > 1) {
               var bubble_picked = 0; 
               for (var j=0; j<form.elements["scantron_correct_Q_"+ansnumArray[i]].length; j++) {
                   if (form.elements["scantron_correct_Q_"+ansnumArray[i]][j].checked == true) {
                       bubble_picked = 1;
                   }
               }
               if (bubble_picked == 0) {
                   need_bubble_count ++;
               }
           }
       }
       if (need_bubble_count) {
           alert("$warning");
           return;
       }
       form.submit(); 
   }
   ENDSCRIPT
       return $output;
 }  }
   
 =pod  =pod
   
 =item scantron_bubble_selector  =item  questions_to_line_list
     
    Generates the html radiobuttons to correct a single bubble line  
    possibly showing the existing the selected bubbles if known  
   
  Arguments:  Converts a list of questions into a string of comma separated
     $r           - Apache request object  line numbers in the answer sheet used by the questions.  This is
     $scan_config - hash from &get_scantron_config()  used to fill in the scantron_questions form field.
     $quest       - number of the bubble line to make a corrector for  
     $lines       - array of answer lines.  
   
 =cut    Arguments:
        questions    - Reference to an array of questions.
        randomorder  - True if randomorder in use.
        randompick   - True if randompick in use.
        respnumlookup - Reference to HASH mapping question numbers in bubble lines
                        for current line to question number used for same question
                        in "Master Seqence" (as seen by Course Coordinator).
        startline    - Reference to hash where key is question number (0 is first)
                       and key is number of first bubble line for current student
                       or code-based randompick and/or randomorder.
   
 sub scantron_bubble_selector {  =cut
     my ($r,$scan_config,$quest,@lines)=@_;  
     my $max=$$scan_config{'Qlength'};  
   
   
     my $scmode=$$scan_config{'Qon'};  sub questions_to_line_list {
       my ($questions,$randomorder,$randompick,$respnumlookup,$startline) = @_;
       my @lines;
   
       foreach my $item (@{$questions}) {
           my $question = $item;
           my ($first,$count,$last);
           if ($item =~ /^(\d+)\.(\d+)$/) {
               $question = $1;
               my $subquestion = $2;
               my $responsenum = $question-1;
               if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   $responsenum = $respnumlookup->{$question-1};
                   if (ref($startline) eq 'HASH') {
                       $first = $startline->{$question-1} + 1;
                   }
               } else {
                   $first = $first_bubble_line{$responsenum} + 1;
               }
               my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
               my $subcount = 1;
               while ($subcount<$subquestion) {
                   $first += $subans[$subcount-1];
                   $subcount ++;
               }
               $count = $subans[$subquestion-1];
           } else {
               my $responsenum = $question-1;
               if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   $responsenum = $respnumlookup->{$question-1};
                   if (ref($startline) eq 'HASH') {
                       $first = $startline->{$question-1} + 1;
                   }
               } else {
                   $first = $first_bubble_line{$responsenum} + 1;
               }
       $count   = $bubble_lines_per_response{$responsenum};
           }
           $last = $first+$count-1;
           push(@lines, ($first..$last));
       }
       return join(',', @lines);
   }
   
     my $bubble_length = scalar(@lines);  =pod 
   
   =item prompt_for_corrections
   
     if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }       Prompts for a potentially multiline correction to the
   user's bubbling (factors out common code from scantron_get_correction
   for multi and missing bubble cases).
   
     my $response = $quest-1;   Arguments:
     my $lines = $bubble_lines_per_response{$response};     $r           - Apache request object.
      $question    - The question number to prompt for.
      $scan_config - The scantron file configuration hash.
      $scan_record - Reference to the hash that has the the parsed scanlines.
      $error       - Type of error
      $randomorder - True if randomorder in use.
      $randompick  - True if randompick in use.
      $respnumlookup - Reference to HASH mapping question numbers in bubble lines
                       for current line to question number used for same question
                       in "Master Seqence" (as seen by Course Coordinator).
      $startline   - Reference to hash where key is question number (0 is first)
                     and value is number of first bubble line for current student
                     or code-based randompick and/or randomorder.
   
   
    Implicit inputs:
      %bubble_lines_per_response   - Starting line numbers for each question.
                                     Numbered from 0 (but question numbers are from
                                     1.
      %first_bubble_line           - Starting bubble line for each question.
      %subdivided_bubble_lines     - optionresponse, matchresponse and rankresponse 
                                     type problems render as separate sub-questions, 
                                     in exam mode. This hash contains a 
                                     comma-separated list of the lines per 
                                     sub-question.
      %responsetype_per_response   - essayresponse, formularesponse,
                                     stringresponse, imageresponse, reactionresponse,
                                     and organicresponse type problem parts can have
                                     multiple lines per response if the weight
                                     assigned exceeds 10.  In this case, only
                                     one bubble per line is permitted, but more 
                                     than one line might contain bubbles, e.g.
                                     bubbling of: line 1 - J, line 2 - J, 
                                     line 3 - B would assign 22 points.  
   
     my $total_lines = $lines*2;  =cut
     my @alphabet=('A'..'Z');  
     $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");  
   
     for (my $l = 0; $l < $lines; $l++) {  sub prompt_for_corrections {
  if ($l != 0) {      my ($r, $question, $scan_config, $scan_record, $error, $randomorder,
     $r->print('<tr>');          $randompick, $respnumlookup, $startline) = @_;
  }      my ($current_line,$lines);
  my @selected = split(//,$lines[$l]);      my @linenums;
  for (my $i=0;$i<$max;$i++) {      my $questionnum = $question;
     $r->print("\n".'<td align="center">');      my ($first,$responsenum);
     if ($selected[0] eq $alphabet[$i]) {       if ($question =~ /^(\d+)\.(\d+)$/) {
  $r->print('X');           $question = $1;
  shift(@selected) ;          my $subquestion = $2;
     } else {           if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
  $r->print('&nbsp;');               $responsenum = $respnumlookup->{$question-1};
     }              if (ref($startline) eq 'HASH') {
     $r->print('</td>');                  $first = $startline->{$question-1};
                   }
  }          } else {
               $responsenum = $question-1;
               $first = $first_bubble_line{$responsenum};
           }
           $current_line = $first + 1 ;
           my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
           my $subcount = 1;
           while ($subcount<$subquestion) {
               $current_line += $subans[$subcount-1];
               $subcount ++;
           }
           $lines = $subans[$subquestion-1];
       } else {
           if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
               $responsenum = $respnumlookup->{$question-1};
               if (ref($startline) eq 'HASH') { 
                   $first = $startline->{$question-1};
               }
           } else {
               $responsenum = $question-1;
               $first = $first_bubble_line{$responsenum};
           }
           $current_line = $first + 1;
           $lines        = $bubble_lines_per_response{$responsenum};
       }
       if ($lines > 1) {
           $r->print(&mt('The group of bubble lines below responds to a single question.').'<br />');
           if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
               ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
               ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
               ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
               ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
               ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
               $r->print(
                   &mt("Although this particular question type requires handgrading, the instructions for this question in the bubblesheet exam directed students to leave [quant,_1,line] blank on their bubblesheets.",$lines)
                  .'<br /><br />'
                  .&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.')
                  .'<br />'
                  .&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.')
                  .'<br />'
                  .&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.")
                  .'<br /><br />'
               );
           } else {
               $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");
           }
       }
       for (my $i =0; $i < $lines; $i++) {
           my $selected = $$scan_record{"scantron.$current_line.answer"};
    &scantron_bubble_selector($r,$scan_config,$current_line,
             $questionnum,$error,split('', $selected));
           push(@linenums,$current_line);
    $current_line++;
       }
       if ($lines > 1) {
    $r->print("<hr /><br />");
       }
       return @linenums;
   }
   
  if ($l == 0) {  =pod
     my $lspan = $total_lines * 2;   #  2 table rows per bubble line.  
   
     $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'.  =item scantron_bubble_selector
       $quest.'" value="none" /> No bubble </label></td>');    
      Generates the html radiobuttons to correct a single bubble line
  }     possibly showing the existing the selected bubbles if known
   
  $r->print('</tr><tr>');   Arguments:
       $r           - Apache request object
       $scan_config - hash from &Apache::lonnet::get_scantron_config()
       $line        - Number of the line being displayed.
       $questionnum - Question number (may include subquestion)
       $error       - Type of error.
       @selected    - Array of bubbles picked on this line.
   
  # FIXME: This may have to be a bit more clever for  =cut
  #        multiline questions (different values e.g..).  
   
  for (my $i=0;$i<$max;$i++) {  sub scantron_bubble_selector {
     $r->print("\n".      my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
       '<td><label><input type="radio" name="scantron_correct_Q_'.      my $max=$$scan_config{'Qlength'};
       $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");  
  }  
  $r->print('</tr>');  
   
           my $scmode=$$scan_config{'Qon'};
       if ($scmode eq 'number' || $scmode eq 'letter') { 
           if (($$scan_config{'BubblesPerRow'} =~ /^\d+$/) &&
               ($$scan_config{'BubblesPerRow'} > 0)) {
               $max=$$scan_config{'BubblesPerRow'};
               if (($scmode eq 'number') && ($max > 10)) {
                   $max = 10;
               } elsif (($scmode eq 'letter') && $max > 26) {
                   $max = 26;
               }
           } else {
               $max = 10;
           }
     }      }
     $r->print('</table>');  
       my @alphabet=('A'..'Z');
       $r->print(&Apache::loncommon::start_data_table().
                 &Apache::loncommon::start_data_table_row());
       $r->print('<td rowspan="2" class="LC_leftcol_header">'.$line.'</td>');
       for (my $i=0;$i<$max+1;$i++) {
    $r->print("\n".'<td align="center">');
    if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
    else { $r->print('&nbsp;'); }
    $r->print('</td>');
       }
       $r->print(&Apache::loncommon::end_data_table_row().
                 &Apache::loncommon::start_data_table_row());
       for (my $i=0;$i<$max;$i++) {
    $r->print("\n".
     '<td><label><input type="radio" name="scantron_correct_Q_'.
     $line.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
       }
       my $nobub_checked = ' ';
       if ($error eq 'missingbubble') {
           $nobub_checked = ' checked = "checked" ';
       }
       $r->print("\n".'<td><label><input type="radio" name="scantron_correct_Q_'.
         $line.'" value="none"'.$nobub_checked.'/>'.&mt('No bubble').
                 '</label>'."\n".'<input type="hidden" name="scantron_questionnum_Q_'.
                 $line.'" value="'.$questionnum.'" /></td>');
       $r->print(&Apache::loncommon::end_data_table_row().
                 &Apache::loncommon::end_data_table());
 }  }
   
 =pod  =pod
Line 6650  sub get_codes { Line 8214  sub get_codes {
   
 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=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     if ($scantron_config{'CODElocation'} &&      if ($scantron_config{'CODElocation'} &&
  $scantron_config{'CODEstart'} &&   $scantron_config{'CODEstart'} &&
  $scantron_config{'CODElength'}) {   $scantron_config{'CODElength'}) {
Line 6665  sub scantron_validate_CODE { Line 8229  sub scantron_validate_CODE {
   
     my %allcodes=&get_codes();      my %allcodes=&get_codes();
   
     &scantron_get_maxbubble(); # parse needs the lines per response array.      my $nav_error;
       &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the lines per response array.
       if ($nav_error) {
           $r->print(&navmap_errormsg());
           return(1,$currentphase);
       }
   
     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++) {
Line 6696  sub scantron_validate_CODE { Line 8265  sub scantron_validate_CODE {
      $line,'duplicateCODE',$usedCODEs{$CODE});       $line,'duplicateCODE',$usedCODEs{$CODE});
     return(1,$currentphase);      return(1,$currentphase);
  }   }
  push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});   push(@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
     }      }
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
Line 6715  sub scantron_validate_doublebubble { Line 8284  sub scantron_validate_doublebubble {
     #get student info      #get student info
     my $classlist=&Apache::loncoursedata::get_classlist();      my $classlist=&Apache::loncoursedata::get_classlist();
     my %idmap=&username_to_idmap($classlist);      my %idmap=&username_to_idmap($classlist);
       my (undef,undef,$sequence)=
           &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
     #get scantron line setup      #get scantron line setup
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&Apache::lonnet::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.      my $navmap = Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $r->print(&navmap_errormsg());
           return(1,$currentphase);
       }
       my $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
       my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
           %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
       my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
   
       my $nav_error;
       if (ref($map)) {
           $randomorder = $map->randomorder();
           $randompick = $map->randompick();
           if ($randomorder || $randompick) {
               $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
               if ($nav_error) {
                   $r->print(&navmap_errormsg());
                   return(1,$currentphase);
               }
               &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                                       \%grader_randomlists_by_symb,$bubbles_per_row);
           }
       } else {
           $r->print(&navmap_errormsg());
           return(1,$currentphase);
       }
   
       &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble line array.
       if ($nav_error) {
           $r->print(&navmap_errormsg());
           return(1,$currentphase);
       }
   
     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; }
  my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,   my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
  $scan_data);   $scan_data,undef,\%idmap,$randomorder,
                                                    $randompick,$sequence,\@master_seq,
                                                    \%symb_to_resource,\%grader_partids_by_symb,
                                                    \%orderedforcode,\%respnumlookup,\%startline);
  if (!defined($$scan_record{'scantron.doubleerror'})) { next; }   if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
  &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,   &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
  'doublebubble',   'doublebubble',
  $$scan_record{'scantron.doubleerror'});   $$scan_record{'scantron.doubleerror'},
                                    $randomorder,$randompick,\%respnumlookup,\%startline);
     return (1,$currentphase);      return (1,$currentphase);
     }      }
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
 =pod  
   
 =item scantron_get_maxbubble  
   
    Returns the maximum number of bubble lines that are expected to  sub scantron_get_maxbubble {
    occur. Does this by walking the selected sequence rendering the      my ($nav_error,$scantron_config) = @_;
    resource and then checking &Apache::lonxml::get_problem_counter()  
    for what the current value of the problem counter is.  
   
    Caches the results to $env{'form.scantron_maxbubble'},  
    $env{'form.scantron.bubble_lines.n'} and   
    $env{'form.scantron.first_bubble_line.n'}  
    which are the total number of bubble, lines, the number of bubble  
    lines for reponse n and number of the first bubble line for response n.  
   
 =cut  
   
 sub scantron_get_maxbubble {      
     if (defined($env{'form.scantron_maxbubble'}) &&      if (defined($env{'form.scantron_maxbubble'}) &&
  $env{'form.scantron_maxbubble'}) {   $env{'form.scantron_maxbubble'}) {
  &restore_bubble_lines();   &restore_bubble_lines();
Line 6764  sub scantron_get_maxbubble { Line 8357  sub scantron_get_maxbubble {
  &Apache::lonnet::decode_symb($env{'form.selectpage'});   &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
     my $navmap=Apache::lonnavmaps::navmap->new();      my $navmap=Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           if (ref($nav_error)) {
               $$nav_error = 1;
           }
           return;
       }
     my $map=$navmap->getResourceByUrl($sequence);      my $map=$navmap->getResourceByUrl($sequence);
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);      my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
       my $bubbles_per_row = &bubblesheet_bubbles_per_row($scantron_config);
   
     &Apache::lonxml::clear_problem_counter();      &Apache::lonxml::clear_problem_counter();
   
     my $uname       = $env{'form.student'};      my $uname       = $env{'user.name'};
     my $udom        = $env{'form.userdom'};      my $udom        = $env{'user.domain'};
     my $cid         = $env{'request.course.id'};      my $cid         = $env{'request.course.id'};
     my $total_lines = 0;      my $total_lines = 0;
     %bubble_lines_per_response = ();      %bubble_lines_per_response = ();
     %first_bubble_line         = ();      %first_bubble_line         = ();
       %subdivided_bubble_lines   = ();
       %responsetype_per_response = ();
       %masterseq_id_responsenum  = ();
   
     
     my $response_number = 0;      my $response_number = 0;
     my $bubble_line     = 0;      my $bubble_line     = 0;
     foreach my $resource (@resources) {      foreach my $resource (@resources) {
  my $symb = $resource->symb();          my $resid = $resource->id(); 
  &Apache::lonxml::clear_bubble_lines_for_part();          my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,
  my $result=&Apache::lonnet::ssi($resource->src(),                                                            $udom,undef,$bubbles_per_row);
  ('symb' => $resource->symb()),          if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
  ('grade_target' => 'analyze'),      foreach my $part_id (@{$parts}) {
  ('grade_courseid' => $cid),                  my $lines;
  ('grade_domain' => $udom),  
  ('grade_username' => $uname));          # TODO - make this a persistent hash not an array.
  my (undef, $an) =  
     split(/_HASH_REF__/,$result, 2);                  # optionresponse, matchresponse and rankresponse type items 
                   # render as separate sub-questions in exam mode.
  my %analysis = &Apache::lonnet::str2hash($an);                  if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
                       ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
                       ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
                       my ($numbub,$numshown);
  foreach my $part_id (@{$analysis{'parts'}}) {                      if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
                           if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
                               $numbub = scalar(@{$analysis->{$part_id.'.options'}});
     my $lines = $analysis{"$part_id.bubble_lines"};;                          }
                       } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
     # TODO - make this a persistent hash not an array.                          if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
                               $numbub = scalar(@{$analysis->{$part_id.'.items'}});
                           }
     $first_bubble_line{$response_number}           = $bubble_line;                      } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
     $bubble_lines_per_response{$response_number}   = $lines;                          if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
     $response_number++;                              $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
                           }
                       }
                       if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
                           $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
                       }
                       my $bubbles_per_row =
                           &bubblesheet_bubbles_per_row($scantron_config);
                       my $inner_bubble_lines = int($numbub/$bubbles_per_row);
                       if (($numbub % $bubbles_per_row) != 0) {
                           $inner_bubble_lines++;
                       }
                       for (my $i=0; $i<$numshown; $i++) {
                           $subdivided_bubble_lines{$response_number} .= 
                               $inner_bubble_lines.',';
                       }
                       $subdivided_bubble_lines{$response_number} =~ s/,$//;
                       $lines = $numshown * $inner_bubble_lines;
                   } else {
                       $lines = $analysis->{"$part_id.bubble_lines"};
                   }
   
     $bubble_line +=  $lines;                  $first_bubble_line{$response_number} = $bubble_line;
     $total_lines +=  $lines;          $bubble_lines_per_response{$response_number} = $lines;
  }                  $responsetype_per_response{$response_number} = 
                       $analysis->{$part_id.'.type'};
                   $masterseq_id_responsenum{$resid.'_'.$part_id} = $response_number;  
           $response_number++;
   
           $bubble_line +=  $lines;
           $total_lines +=  $lines;
       }
           }
     }      }
     &Apache::lonnet::delenv('scantron\.');      &Apache::lonnet::delenv('scantron.');
   
     &save_bubble_lines();      &save_bubble_lines();
     $env{'form.scantron_maxbubble'} =      $env{'form.scantron_maxbubble'} =
Line 6820  sub scantron_get_maxbubble { Line 8449  sub scantron_get_maxbubble {
     return $env{'form.scantron_maxbubble'};      return $env{'form.scantron_maxbubble'};
 }  }
   
 =pod  sub bubblesheet_bubbles_per_row {
       my ($scantron_config) = @_;
 =item scantron_validate_missingbubbles      my $bubbles_per_row;
       if (ref($scantron_config) eq 'HASH') {
    Validates all scanlines in the selected file to not have any          $bubbles_per_row = $scantron_config->{'BubblesPerRow'};
     answers that don't have bubbles that have not been verified      }
     to be bubble free.      if ((!$bubbles_per_row) || ($bubbles_per_row < 1)) {
           $bubbles_per_row = 10;
 =cut      }
       return $bubbles_per_row;
   }
   
 sub scantron_validate_missingbubbles {  sub scantron_validate_missingbubbles {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
     #get student info      #get student info
     my $classlist=&Apache::loncoursedata::get_classlist();      my $classlist=&Apache::loncoursedata::get_classlist();
     my %idmap=&username_to_idmap($classlist);      my %idmap=&username_to_idmap($classlist);
       my (undef,undef,$sequence)=
           &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
     #get scantron line setup      #get scantron line setup
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
     my $max_bubble=&scantron_get_maxbubble();  
       my $navmap = Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $r->print(&navmap_errormsg());
           return(1,$currentphase);
       }
   
       my $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
       my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
           %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
       my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
   
       my $nav_error;
       if (ref($map)) {
           $randomorder = $map->randomorder();
           $randompick = $map->randompick();
           if ($randomorder || $randompick) {
               $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
               if ($nav_error) {
                   $r->print(&navmap_errormsg());
                   return(1,$currentphase);
               }
               &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                                       \%grader_randomlists_by_symb,$bubbles_per_row);
           }
       } else {
           $r->print(&navmap_errormsg());
           return(1,$currentphase);
       }
   
   
       my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
       if ($nav_error) {
           $r->print(&navmap_errormsg());
           return(1,$currentphase);
       }
   
     if (!$max_bubble) { $max_bubble=2**31; }      if (!$max_bubble) { $max_bubble=2**31; }
     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; }
  my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,   my $scan_record =
  $scan_data);              &scantron_parse_scanline($line,$i,\%scantron_config,$scan_data,undef,\%idmap,
        $randomorder,$randompick,$sequence,\@master_seq,
                                        \%symb_to_resource,\%grader_partids_by_symb,
                                        \%orderedforcode,\%respnumlookup,\%startline);
  if (!defined($$scan_record{'scantron.missingerror'})) { next; }   if (!defined($$scan_record{'scantron.missingerror'})) { next; }
  my @to_correct;   my @to_correct;
   
    # Probably here's where the error is...
   
  foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {   foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
     if ($missing > $max_bubble) { next; }              my $lastbubble;
               if ($missing =~ /^(\d+)\.(\d+)$/) {
                  my $question = $1;
                  my $subquestion = $2;
                  my ($first,$responsenum);
                  if ($randomorder || $randompick) {
                      $responsenum = $respnumlookup{$question-1};
                      $first = $startline{$question-1};
                  } else {
                      $responsenum = $question-1; 
                      $first = $first_bubble_line{$responsenum};
                  }
                  if (!defined($first)) { next; }
                  my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
                  my $subcount = 1;
                  while ($subcount<$subquestion) {
                      $first += $subans[$subcount-1];
                      $subcount ++;
                  }
                  my $count = $subans[$subquestion-1];
                  $lastbubble = $first + $count;
               } else {
                  my ($first,$responsenum);
                  if ($randomorder || $randompick) {
                      $responsenum = $respnumlookup{$missing-1};
                      $first = $startline{$missing-1};
                  } else {
                      $responsenum = $missing-1;
                      $first = $first_bubble_line{$responsenum};
                  }
                  if (!defined($first)) { next; }
                  $lastbubble = $first + $bubble_lines_per_response{$responsenum};
               }
               if ($lastbubble > $max_bubble) { next; }
     push(@to_correct,$missing);      push(@to_correct,$missing);
  }   }
  if (@to_correct) {   if (@to_correct) {
     &scantron_get_correction($r,$i,$scan_record,\%scantron_config,      &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
      $line,'missingbubble',\@to_correct);       $line,'missingbubble',\@to_correct,
                                        $randomorder,$randompick,\%respnumlookup,
                                        \%startline);
     return (1,$currentphase);      return (1,$currentphase);
  }   }
   
Line 6862  sub scantron_validate_missingbubbles { Line 8573  sub scantron_validate_missingbubbles {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
 =pod  sub hand_bubble_option {
       my (undef, undef, $sequence) =
 =item scantron_process_students          &Apache::lonnet::decode_symb($env{'form.selectpage'});
       return if ($sequence eq '');
    Routine that does the actual grading of the bubble sheet information.      my $navmap = Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
    The parsed scanline hash is added to %env           return;
       }
    Then foreach unskipped scanline it does an &Apache::lonnet::ssi()      my $needs_hand_bubbles;
    foreach resource , with the form data of      my $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
  'submitted'     =>'scantron'       foreach my $res (@resources) {
  'grade_target'  =>'grade',          if (ref($res)) {
  'grade_username'=> username of student              if ($res->is_problem()) {
  'grade_domain'  => domain of student                  my $partlist = $res->parts();
  'grade_courseid'=> of course                  foreach my $part (@{ $partlist }) {
  'grade_symb'    => symb of resource to grade                      my @types = $res->responseType($part);
                       if (grep(/^(chem|essay|image|formula|math|string|functionplot)$/,@types)) {
     This triggers a grading pass. The problem grading code takes care                          $needs_hand_bubbles = 1;
     of converting the bubbled letter information (now in %env) into a                          last;
     valid submission.                      }
                   }
 =cut              }
           }
       }
       if ($needs_hand_bubbles) {
           my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
           my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
           return &mt('The sequence to be graded contains response types which are handgraded.').'<p>'.
                  &mt('If you have already graded these by bubbling sheets to indicate points awarded, [_1]what point value is assigned to a filled last bubble in each row?','<br />').
                  '<label><input type="radio" name="scantron_lastbubblepoints" value="'.$bubbles_per_row.'" checked="checked" />'.&mt('[quant,_1,point]',$bubbles_per_row).'</label>&nbsp;'.&mt('or').'&nbsp;'.
                  '<label><input type="radio" name="scantron_lastbubblepoints" value="0" />'.&mt('0 points').'</label></p>';
       }
       return;
   }
   
 sub scantron_process_students {  sub scantron_process_students {
     my ($r) = @_;      my ($r,$symb) = @_;
   
     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});      my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
     my ($symb)=&get_symb($r);      if (!$symb) {
     if (!$symb) {return '';}   return '';
       }
     my $default_form_data=&defaultFormData($symb);      my $default_form_data=&defaultFormData($symb);
   
     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});      my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
       my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config); 
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
     my $classlist=&Apache::loncoursedata::get_classlist();      my $classlist=&Apache::loncoursedata::get_classlist();
     my %idmap=&username_to_idmap($classlist);      my %idmap=&username_to_idmap($classlist);
     my $navmap=Apache::lonnavmaps::navmap->new();      my $navmap=Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $r->print(&navmap_errormsg());
           return '';
       }
     my $map=$navmap->getResourceByUrl($sequence);      my $map=$navmap->getResourceByUrl($sequence);
       my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
           %grader_randomlists_by_symb);
       if (ref($map)) {
           $randomorder = $map->randomorder();
           $randompick = $map->randompick();
       } else {
           $r->print(&navmap_errormsg());
           return '';
       }
       my $nav_error;
     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);      my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 #    $r->print("geto ".scalar(@resources)."<br />");      if ($randomorder || $randompick) {
           $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
           if ($nav_error) {
               $r->print(&navmap_errormsg());
               return '';
           }
       }
       &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                               \%grader_randomlists_by_symb,$bubbles_per_row);
   
       my ($uname,$udom);
     my $result= <<SCANTRONFORM;      my $result= <<SCANTRONFORM;
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
   <input type="hidden" name="command" value="scantron_configphase" />    <input type="hidden" name="command" value="scantron_configphase" />
Line 6909  SCANTRONFORM Line 8659  SCANTRONFORM
     $r->print($result);      $r->print($result);
   
     my @delayqueue;      my @delayqueue;
     my %completedstudents;      my (%completedstudents,%scandata);
           
       my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));
     my $count=&get_todo_count($scanlines,$scan_data);      my $count=&get_todo_count($scanlines,$scan_data);
     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',      my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
      'Scantron Progress',$count,      &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
     'inline',undef,'scantronupload');      $r->print('<br />');
     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,  
   'Processing first student');  
     my $start=&Time::HiRes::time();      my $start=&Time::HiRes::time();
     my $i=-1;      my $i=-1;
     my ($uname,$udom,$started);      my $started;
   
     &scantron_get_maxbubble(); # Need the bubble lines array to parse.      &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
       if ($nav_error) {
           $r->print(&navmap_errormsg());
           return '';
       }
   
       # If an ssi failed in scantron_get_maxbubble, put an error message out to
       # the user and return.
   
       if ($ssi_error) {
    $r->print("</form>");
    &ssi_print_error($r);
           &Apache::lonnet::remove_lock($lock);
    return ''; # Dunno why the other returns return '' rather than just returning.
       }
   
       my %lettdig = &Apache::lonnet::letter_to_digits();
       my $numletts = scalar(keys(%lettdig));
       my %orderedforcode;
   
     while ($i<$scanlines->{'count'}) {      while ($i<$scanlines->{'count'}) {
   ($uname,$udom)=('','');    ($uname,$udom)=('','');
Line 6929  SCANTRONFORM Line 8696  SCANTRONFORM
   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; }
  if ($started) {   if ($started) {
     &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,      &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
      'last student');  
  }   }
  $started=1;   $started=1;
           my %respnumlookup = ();
           my %startline = ();
           my $total;
   my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,    my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
   $scan_data);                                                   $scan_data,undef,\%idmap,$randomorder,
                                                    $randompick,$sequence,\@master_seq,
                                                    \%symb_to_resource,\%grader_partids_by_symb,
                                                    \%orderedforcode,\%respnumlookup,\%startline,
                                                    \$total);
   unless ($uname=&scantron_find_student($scan_record,$scan_data,    unless ($uname=&scantron_find_student($scan_record,$scan_data,
        \%idmap,$i)) {         \%idmap,$i)) {
      &scantron_add_delay(\@delayqueue,$line,       &scantron_add_delay(\@delayqueue,$line,
Line 6946  SCANTRONFORM Line 8719  SCANTRONFORM
   'Student '.$uname.' has multiple sheets',2);    'Student '.$uname.' has multiple sheets',2);
      next;       next;
   }    }
           my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
           my $user = $uname.':'.$usec;
   ($uname,$udom)=split(/:/,$uname);    ($uname,$udom)=split(/:/,$uname);
   
           my $scancode;
           if ((exists($scan_record->{'scantron.CODE'})) &&
               (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
               $scancode = $scan_record->{'scantron.CODE'};
           } else {
               $scancode = '';
           }
   
           my @mapresources = @resources;
           if ($randomorder || $randompick) {
               @mapresources = 
                   &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
                                \%orderedforcode);
           }
           my (%partids_by_symb,$res_error);
           foreach my $resource (@mapresources) {
               my $ressymb;
               if (ref($resource)) {
                   $ressymb = $resource->symb();
               } else {
                   $res_error = 1;
                   last;
               }
               if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
                   my $currcode;
                   if (exists($grader_randomlists_by_symb{$ressymb})) {
                       $currcode = $scancode;
                   }
                   my ($analysis,$parts) =
                       &scantron_partids_tograde($resource,$env{'request.course.id'},
                                                 $uname,$udom,undef,$bubbles_per_row,
                                                 $currcode);
                   $partids_by_symb{$ressymb} = $parts;
               } else {
                   $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
               }
           }
   
           if ($res_error) {
               &scantron_add_delay(\@delayqueue,$line,
                                   'An error occurred while grading student '.$uname,2);
               next;
           }
   
  &Apache::lonxml::clear_problem_counter();   &Apache::lonxml::clear_problem_counter();
   &Apache::lonnet::appenv(%$scan_record);    &Apache::lonnet::appenv($scan_record);
   
  if (&scantron_clear_skip($scanlines,$scan_data,$i)) {   if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
     &scantron_putfile($scanlines,$scan_data);      &scantron_putfile($scanlines,$scan_data);
  }   }
   
  my $i=0;          if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
  foreach my $resource (@resources) {                                     \@mapresources,\%partids_by_symb,
     $i++;                                     $bubbles_per_row,$randomorder,$randompick,
     my %form=('submitted'     =>'scantron',                                     \%respnumlookup,\%startline) 
       'grade_target'  =>'grade',              eq 'ssi_error') {
       'grade_username'=>$uname,              $ssi_error = 0; # So end of handler error message does not trigger.
       'grade_domain'  =>$udom,              $r->print("</form>");
       'grade_courseid'=>$env{'request.course.id'},              &ssi_print_error($r);
       'grade_symb'    =>$resource->symb());              &Apache::lonnet::remove_lock($lock);
     if (exists($scan_record->{'scantron.CODE'})              return '';      # Why return ''?  Beats me.
  &&           }
  &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {  
  $form{'CODE'}=$scan_record->{'scantron.CODE'};          if (($scancode) && ($randomorder || $randompick)) {
     } else {              my $parmresult =
  $form{'CODE'}='';                  &Apache::lonparmset::storeparm_by_symb($symb,
     }                                                         '0_examcode',2,$scancode,
     my $result=&Apache::lonnet::ssi($resource->src(),%form);                                                         'string_examcode',$uname,
     if ($result ne '') {                                                         $udom);
     }          }
     if (&Apache::loncommon::connection_aborted($r)) { last; }  
  }  
  $completedstudents{$uname}={'line'=>$line};   $completedstudents{$uname}={'line'=>$line};
  if (&Apache::loncommon::connection_aborted($r)) { last; }          if ($env{'form.verifyrecord'}) {
               my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
               if ($randompick) {
                   if ($total) {
                       $lastpos = $total*$scantron_config{'Qlength'};
                   }
               }
   
               my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
               chomp($studentdata);
               $studentdata =~ s/\r$//;
               my $studentrecord = '';
               my $counter = -1;
               foreach my $resource (@mapresources) {
                   my $ressymb = $resource->symb();
                   ($counter,my $recording) =
                       &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
                                                $counter,$studentdata,$partids_by_symb{$ressymb},
                                                \%scantron_config,\%lettdig,$numletts,$randomorder,
                                                $randompick,\%respnumlookup,\%startline);
                   $studentrecord .= $recording;
               }
               if ($studentrecord ne $studentdata) {
                   &Apache::lonxml::clear_problem_counter();
                   if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
                                              \@mapresources,\%partids_by_symb,
                                              $bubbles_per_row,$randomorder,$randompick,
                                              \%respnumlookup,\%startline) 
                       eq 'ssi_error') {
                       $ssi_error = 0; # So end of handler error message does not trigger.
                       $r->print("</form>");
                       &ssi_print_error($r);
                       &Apache::lonnet::remove_lock($lock);
                       delete($completedstudents{$uname});
                       return '';
                   }
                   $counter = -1;
                   $studentrecord = '';
                   foreach my $resource (@mapresources) {
                       my $ressymb = $resource->symb();
                       ($counter,my $recording) =
                           &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
                                                    $counter,$studentdata,$partids_by_symb{$ressymb},
                                                    \%scantron_config,\%lettdig,$numletts,
                                                    $randomorder,$randompick,\%respnumlookup,
                                                    \%startline);
                       $studentrecord .= $recording;
                   }
                   if ($studentrecord ne $studentdata) {
                       $r->print('<p><span class="LC_warning">');
                       if ($scancode eq '') {
                           $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2].',
                                     $uname.':'.$udom,$scan_record->{'scantron.ID'}));
                       } else {
                           $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2] and CODE: [_3].',
                                     $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
                       }
                       $r->print('</span><br />'.&Apache::loncommon::start_data_table()."\n".
                                 &Apache::loncommon::start_data_table_header_row()."\n".
                                 '<th>'.&mt('Source').'</th><th>'.&mt('Bubbled responses').'</th>'.
                                 &Apache::loncommon::end_data_table_header_row()."\n".
                                 &Apache::loncommon::start_data_table_row().
                                 '<td>'.&mt('Bubblesheet').'</td>'.
                                 '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentdata.'</tt></span></td>'.
                                 &Apache::loncommon::end_data_table_row().
                                 &Apache::loncommon::start_data_table_row().
                                 '<td>'.&mt('Stored submissions').'</td>'.
                                 '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentrecord.'</tt></span></td>'."\n".
                                 &Apache::loncommon::end_data_table_row().
                                 &Apache::loncommon::end_data_table().'</p>');
                   } else {
                       $r->print('<br /><span class="LC_warning">'.
                                &mt('A second grading pass was needed for user: [_1] with ID: [_2], because a mismatch was seen on the first pass.',$uname.':'.$udom,$scan_record->{'scantron.ID'}).'<br />'.
                                &mt("As a consequence, this user's submission history records two tries.").
                                    '</span><br />');
                   }
               }
           }
           if (&Apache::loncommon::connection_aborted($r)) { last; }
     } continue {      } continue {
  &Apache::lonxml::clear_problem_counter();   &Apache::lonxml::clear_problem_counter();
  &Apache::lonnet::delenv('scantron\.');   &Apache::lonnet::delenv('scantron.');
     }      }
     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);      &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       &Apache::lonnet::remove_lock($lock);
 #    my $lasttime = &Time::HiRes::time()-$start;  #    my $lasttime = &Time::HiRes::time()-$start;
 #    $r->print("<p>took $lasttime</p>");  #    $r->print("<p>took $lasttime</p>");
   
     $r->print("</form>");      $r->print("</form>");
     $r->print(&show_grading_menu_form($symb));  
     return '';      return '';
 }  }
   
   sub graders_resources_pass {
       my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb,
           $bubbles_per_row) = @_;
       if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) && 
           (ref($grader_randomlists_by_symb) eq 'HASH')) {
           foreach my $resource (@{$resources}) {
               my $ressymb = $resource->symb();
               my ($analysis,$parts) =
                   &scantron_partids_tograde($resource,$env{'request.course.id'},
                                             $env{'user.name'},$env{'user.domain'},
                                             1,$bubbles_per_row);
               $grader_partids_by_symb->{$ressymb} = $parts;
               if (ref($analysis) eq 'HASH') {
                   if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
                       $grader_randomlists_by_symb->{$ressymb} =
                           $analysis->{'parts_withrandomlist'};
                   }
               }
           }
       }
       return;
   }
   
 =pod  =pod
   
 =item scantron_upload_scantron_data  =item users_order
   
     Returns array of resources in current map, ordered based on either CODE,
     if this is a CODEd exam, or based on student's identity if this is a 
     "NAMEd" exam.
   
     Creates the screen for adding a new bubble sheet data file to a course.    Should be used when randomorder and/or randompick applied when the 
     corresponding exam was printed, prior to students completing bubblesheets 
     for the version of the exam the student received.
   
 =cut  =cut
   
   sub users_order  {
       my ($user,$scancode,$mapurl,$master_seq,$symb_to_resource,$orderedforcode) = @_;
       my @mapresources;
       unless ((ref($master_seq) eq 'ARRAY') && (ref($symb_to_resource) eq 'HASH')) {
           return @mapresources;
       }
       if ($scancode) {
           if ((ref($orderedforcode) eq 'HASH') && (ref($orderedforcode->{$scancode}) eq 'ARRAY')) {
               @mapresources = @{$orderedforcode->{$scancode}};
           } else {
               $env{'form.CODE'} = $scancode;
               my $actual_seq =
                   &Apache::lonprintout::master_seq_to_person_seq($mapurl,
                                                                  $master_seq,
                                                                  $user,$scancode,1);
               if (ref($actual_seq) eq 'ARRAY') {
                   @mapresources = map { $symb_to_resource->{$_}; } @{$actual_seq};
                   if (ref($orderedforcode) eq 'HASH') {
                       if (@mapresources > 0) { 
                           $orderedforcode->{$scancode} = \@mapresources;
                       }
                   }
               }
               delete($env{'form.CODE'});
           }
       } else {
           my $actual_seq =
               &Apache::lonprintout::master_seq_to_person_seq($mapurl,
                                                              $master_seq,
                                                              $user,undef,1);
           if (ref($actual_seq) eq 'ARRAY') {
               @mapresources = 
                   map { $symb_to_resource->{$_}; } @{$actual_seq};
           }
       }
       return @mapresources;
   }
   
   sub grade_student_bubbles {
       my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row,
           $randomorder,$randompick,$respnumlookup,$startline) = @_;
       my $uselookup = 0;
       if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH') &&
           (ref($startline) eq 'HASH')) {
           $uselookup = 1;
       }
   
       if (ref($resources) eq 'ARRAY') {
           my $count = 0;
           foreach my $resource (@{$resources}) {
               my $ressymb = $resource->symb();
               my %form = ('submitted'      => 'scantron',
                           'grade_target'   => 'grade',
                           'grade_username' => $uname,
                           'grade_domain'   => $udom,
                           'grade_courseid' => $env{'request.course.id'},
                           'grade_symb'     => $ressymb,
                           'CODE'           => $scancode
                          );
               if ($bubbles_per_row ne '') {
                   $form{'bubbles_per_row'} = $bubbles_per_row;
               }
               if ($env{'form.scantron_lastbubblepoints'} ne '') {
                   $form{'scantron_lastbubblepoints'} = $env{'form.scantron_lastbubblepoints'};
               }
               if (ref($parts) eq 'HASH') {
                   if (ref($parts->{$ressymb}) eq 'ARRAY') {
                       foreach my $part (@{$parts->{$ressymb}}) {
                           if ($uselookup) {
                               $form{'scantron_questnum_start.'.$part} = $startline->{$count} + 1;
                           } else {
                               $form{'scantron_questnum_start.'.$part} =
                                   1+$env{'form.scantron.first_bubble_line.'.$count};
                           }
                           $count++;
                       }
                   }
               }
               my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form);
               return 'ssi_error' if ($ssi_error);
               last if (&Apache::loncommon::connection_aborted($r));
           }
       }
       return;
   }
   
 sub scantron_upload_scantron_data {  sub scantron_upload_scantron_data {
     my ($r)=@_;      my ($r,$symb)=@_;
     $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));      my $dom = $env{'request.role.domain'};
       my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($dom);
       my $domdesc = &Apache::lonnet::domain($dom,'description');
       $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',      my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
   'domainid',    'domainid',
   'coursename');    'coursename',$dom);
     my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},      my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'.
    'domainid');                         ('&nbsp'x2).&mt('(shows course personnel)'); 
     my $default_form_data=&defaultFormData(&get_symb($r,1));      my $default_form_data=&defaultFormData($symb);
     $r->print(<<UPLOAD);      my $nofile_alert = &mt('Please use the browse button to select a file from your local directory.');
 <script type="text/javascript" language="javascript">      &js_escape(\$nofile_alert);
       my $nocourseid_alert = &mt("Please use the 'Select Course' link to open a separate window where you can search for a course to which a file can be uploaded.");
       &js_escape(\$nocourseid_alert);
       $r->print(&Apache::lonhtmlcommon::scripttag('
     function checkUpload(formname) {      function checkUpload(formname) {
  if (formname.upfile.value == "") {   if (formname.upfile.value == "") {
     alert("Please use the browse button to select a file from your local directory.");      alert("'.$nofile_alert.'");
     return false;      return false;
  }   }
           if (formname.courseid.value == "") {
               alert("'.$nocourseid_alert.'");
               return false;
           }
  formname.submit();   formname.submit();
     }      }
 </script>  
   
 <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>      function ToSyllabus() {
 $default_form_data          var cdom = '."'$dom'".';
 <table>          var cnum = document.rules.courseid.value;
 <tr><td>$select_link </td></tr>          if (cdom == "" || cdom == null) {
 <tr><td>Course ID:   </td><td><input name='courseid' type='text' />  </td></tr>              return;
 <tr><td>Course Name: </td><td><input name='coursename' type='text' /></td></tr>          }
 <tr><td>Domain:      </td><td>$domsel                                </td></tr>          if (cnum == "" || cnum == null) {
 <tr><td>File to upload:</td><td><input type="file" name="upfile" size="50" /></td></tr>             return;
 </table>          }
 <input name='command' value='scantronupload_save' type='hidden' />          syllwin=window.open("/public/"+cdom+"/"+cnum+"/syllabus","LONCAPASyllabus",
 <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />                              "height=350,width=350,scrollbars=yes,menubar=no");
           return;
       }
   
       '.$formatjs.'
   '));
       $r->print('
   <h3>'.&mt('Send bubblesheet data to a course').'</h3>
   
   <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
   '.$default_form_data.
     &Apache::lonhtmlcommon::start_pick_box().
     &Apache::lonhtmlcommon::row_title(&mt('Course ID')).
     '<input name="courseid" type="text" size="30" />'.$select_link.
     &Apache::lonhtmlcommon::row_closure().
     &Apache::lonhtmlcommon::row_title(&mt('Course Name')).
     '<input name="coursename" type="text" size="30" />'.$syllabuslink.
     &Apache::lonhtmlcommon::row_closure().
     &Apache::lonhtmlcommon::row_title(&mt('Domain')).
     '<input name="domainid" type="hidden" />'.$domdesc.
     &Apache::lonhtmlcommon::row_closure());
       if ($formatoptions) {
           $r->print(&Apache::lonhtmlcommon::row_title($formattitle).$formatoptions.
                     &Apache::lonhtmlcommon::row_closure());
       }
       $r->print(
     &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
     '<input type="file" name="upfile" size="50" />'.
     &Apache::lonhtmlcommon::row_closure(1).
     &Apache::lonhtmlcommon::end_pick_box().'<br />
   
   <input name="command" value="scantronupload_save" type="hidden" />
   <input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
 </form>  </form>
 UPLOAD  ');
     return '';      return '';
 }  }
   
 =pod  sub scantron_upload_dataformat {
       my ($dom) = @_;
 =item scantron_upload_scantron_data_save      my ($formatoptions,$formattitle,$formatjs);
       $formatjs = <<'END';
    Adds a provided bubble information data file to the course if user  function toggleScantab(form) {
    has the correct privileges to do so.       return;
   }
   END
       my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$dom);
       if (ref($domconfig{'scantron'}) eq 'HASH') {
           if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
               if (keys(%{$domconfig{'scantron'}{'config'}}) > 1) {
                   if (($domconfig{'scantron'}{'config'}{'dat'}) &&
                       (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH')) {
                       if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {  
                           if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}})) {
                               my ($onclick,$formatextra,$singleline);
                               my @lines = &Apache::lonnet::get_scantronformat_file();
                               my $count = 0;
                               foreach my $line (@lines) {
                                   next if ($line =~ /^#/);
                                   $singleline = $line;
                                   $count ++;
                               }
                               if ($count > 1) {
                                   $formatextra = '<div style="display:none" id="bubbletype">'.
                                                  '<span class="LC_nobreak">'.
                                                  &mt('Bubblesheet type:').'&nbsp;'.
                                                  &scantron_scantab().'</span></div>';
                                   $onclick = ' onclick="toggleScantab(this.form);"';
                                   $formatjs = <<"END";
   function toggleScantab(form) {
       var divid = 'bubbletype';
       if (document.getElementById(divid)) {
           var radioname = 'fileformat';
           var num = form.elements[radioname].length;
           if (num) {
               for (var i=0; i<num; i++) {
                   if (form.elements[radioname][i].checked) {
                       var chosen = form.elements[radioname][i].value;
                       if (chosen == 'dat') {
                           document.getElementById(divid).style.display = 'none';
                       } else if (chosen == 'csv') {
                           document.getElementById(divid).style.display = 'block';
                       }
                   }
               }
           }
       }
       return;
   }
   
 =cut  END
                               } elsif ($count == 1) {
                                   my $formatname = (split(/:/,$singleline,2))[0];
                                   $formatextra = '<input type="hidden" name="scantron_format" value="'.$formatname.'" />';
                               }
                               $formattitle = &mt('File format');
                               $formatoptions = '<label><input name="fileformat" type="radio" value="dat" checked="checked"'.$onclick.' />'.
                                                &mt('Plain Text (no delimiters)').
                                                '</label>'.('&nbsp;'x2).
                                                '<label><input name="fileformat" type="radio" value="csv"'.$onclick.' />'.
                                                &mt('Comma separated values').'</label>'.$formatextra;
                           }
                       }
                   }
               } elsif (keys(%{$domconfig{'scantron'}{'config'}}) == 1) {
                   if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
                       if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}})) {
                           $formattitle = &mt('Bubblesheet type');
                           $formatoptions = &scantron_scantab();
                       }
                   }
               }
           }
       }
       return ($formatoptions,$formattitle,$formatjs);
   }
   
 sub scantron_upload_scantron_data_save {  sub scantron_upload_scantron_data_save {
     my($r)=@_;      my($r,$symb)=@_;
     my ($symb)=&get_symb($r,1);  
     my $doanotherupload=      my $doanotherupload=
  '<br /><form action="/adm/grades" method="post">'."\n".   '<br /><form action="/adm/grades" method="post">'."\n".
  '<input type="hidden" name="command" value="scantronupload" />'."\n".   '<input type="hidden" name="command" value="scantronupload" />'."\n".
  '<input type="submit" name="submit" value="Do Another Upload" />'."\n".   '<input type="submit" name="submit" value="'.&mt('Do Another Upload').'" />'."\n".
  '</form>'."\n";   '</form>'."\n";
     if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&      if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
  !&Apache::lonnet::allowed('usc',   !&Apache::lonnet::allowed('usc',
     $env{'form.domainid'}.'_'.$env{'form.courseid'})) {      $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
  $r->print("You are not allowed to upload Scantron data to the requested course.<br />");   $r->print(&mt("You are not allowed to upload bubblesheet data to the requested course.")."<br />");
  if ($symb) {   unless ($symb) {
     $r->print(&show_grading_menu_form($symb));  
  } else {  
     $r->print($doanotherupload);      $r->print($doanotherupload);
  }   }
  return '';   return '';
     }      }
     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});      my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
     $r->print("Doing upload to ".$coursedata{'description'}." <br />");      my $uploadedfile;
     my $fname=$env{'form.upfile.filename'};      $r->print('<p>'.&mt('Uploading file to [_1]','"'.$coursedata{'description'}.'"').'</p>');
     #FIXME  
     #copied from lonnet::userfileupload()  
     #make that function able to target a specified course  
     # Replace Windows backslashes by forward slashes  
     $fname=~s/\\/\//g;  
     # Get rid of everything but the actual filename  
     $fname=~s/^.*\/([^\/]+)$/$1/;  
     # Replace spaces by underscores  
     $fname=~s/\s+/\_/g;  
     # Replace all other weird characters by nothing  
     $fname=~s/[^\w\.\-]//g;  
     # See if there is anything left  
     unless ($fname) { return 'error: no uploaded file'; }  
     my $uploadedfile=$fname;  
     $fname='scantron_orig_'.$fname;  
     if (length($env{'form.upfile'}) < 2) {      if (length($env{'form.upfile'}) < 2) {
  $r->print("<span class=\"LC_error\">Error:</span> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.");          $r->print(
               &Apache::lonhtmlcommon::confirm_success(
                   &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
                           '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1));
     } else {      } else {
  my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);          my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$env{'form.domainid'});
  if ($result =~ m|^/uploaded/|) {          my $parser;
     $r->print("<span class=\"LC_success\">Success:</span> Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");          if (ref($domconfig{'scantron'}) eq 'HASH') {
  } else {              if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
     $r->print("<span class=\"LC_error\">Error:</span> An error (".$result.") occurred when attempting to upload the file, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>");                  my $is_csv;
                   my @possibles = keys(%{$domconfig{'scantron'}{'config'}});
                   if (@possibles > 1) {
                       if ($env{'form.fileformat'} eq 'csv') {
                           if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
                               if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
                                   if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}}) > 1) {
                                       $is_csv = 1;
                                   }
                               }
                           }
                       }
                   } elsif (@possibles == 1) {
                       if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
                           if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
                               if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}}) > 1) {
                                   $is_csv = 1;
                               }
                           }
                       }
                   }
                   if ($is_csv) {
                      $parser = $domconfig{'scantron'}{'config'}{'csv'};
                   }
               }
           }
           my $result =
               &Apache::lonnet::userfileupload('upfile','scantron','scantron',$parser,'','',
                                               $env{'form.courseid'},$env{'form.domainid'});
           if ($result =~ m{^/uploaded/}) {
               $r->print(
                   &Apache::lonhtmlcommon::confirm_success(&mt('Upload successful')).'<br />'.
                   &mt('Uploaded [_1] bytes of data into location: [_2]',
                           (length($env{'form.upfile'})-1),
                           '<span class="LC_filename">'.$result.'</span>'));
               ($uploadedfile) = ($result =~ m{/([^/]+)$});
               $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
                                                          $env{'form.courseid'},$uploadedfile));
           } else {
               $r->print(
                   &Apache::lonhtmlcommon::confirm_success(&mt('Upload failed'),1).'<br />'.
                       &mt('An error ([_1]) occurred when attempting to upload the file: [_2]',
                             $result,
     '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
  }   }
     }      }
     if ($symb) {      if ($symb) {
  $r->print(&scantron_selectphase($r,$uploadedfile));   $r->print(&scantron_selectphase($r,$uploadedfile,$symb));
     } else {      } else {
  $r->print($doanotherupload);   $r->print($doanotherupload);
     }      }
     return '';      return '';
 }  }
   
 =pod  sub validate_uploaded_scantron_file {
       my ($cdom,$cname,$fname) = @_;
 =item valid_file      my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname);
       my @lines;
    Validates that the requested bubble data file exists in the course.      if ($scanlines ne '-1') {
           @lines=split("\n",$scanlines,-1);
 =cut      }
       my $output;
       if (@lines) {
           my (%counts,$max_match_format);
           my ($found_match_count,$max_match_count,$max_match_pct) = (0,0,0);
           my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
           my %idmap = &username_to_idmap($classlist);
           foreach my $key (keys(%idmap)) {
               my $lckey = lc($key);
               $idmap{$lckey} = $idmap{$key};
           }
           my %unique_formats;
           my @formatlines = &Apache::lonnet::get_scantronformat_file();
           foreach my $line (@formatlines) {
               chomp($line);
               my @config = split(/:/,$line);
               my $idstart = $config[5];
               my $idlength = $config[6];
               if (($idstart ne '') && ($idlength > 0)) {
                   if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') {
                       push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]); 
                   } else {
                       $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]];
                   }
               }
           }
           foreach my $key (keys(%unique_formats)) {
               my ($idstart,$idlength) = split(':',$key);
               %{$counts{$key}} = (
                                  'found'   => 0,
                                  'total'   => 0,
                                 );
               foreach my $line (@lines) {
                   next if ($line =~ /^#/);
                   next if ($line =~ /^[\s\cz]*$/);
                   my $id = substr($line,$idstart-1,$idlength);
                   $id = lc($id);
                   if (exists($idmap{$id})) {
                       $counts{$key}{'found'} ++;
                   }
                   $counts{$key}{'total'} ++;
               }
               if ($counts{$key}{'total'}) {
                   my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'});
                   if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
                       $max_match_pct = $percent_match;
                       $max_match_format = $key;
                       $found_match_count = $counts{$key}{'found'};
                       $max_match_count = $counts{$key}{'total'};
                   }
               }
           }
           if (ref($unique_formats{$max_match_format}) eq 'ARRAY') {
               my $format_descs;
               my $numwithformat = @{$unique_formats{$max_match_format}};
               for (my $i=0; $i<$numwithformat; $i++) {
                   my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]);
                   if ($i<$numwithformat-2) {
                       $format_descs .= '"<i>'.$desc.'</i>", ';
                   } elsif ($i==$numwithformat-2) {
                       $format_descs .= '"<i>'.$desc.'</i>" '.&mt('and').' ';
                   } elsif ($i==$numwithformat-1) {
                       $format_descs .= '"<i>'.$desc.'</i>"';
                   }
               }
               my $showpct = sprintf("%.0f",$max_match_pct).'%';
               $output .= '<br />';
               if ($found_match_count == $max_match_count) {
                   # 100% matching entries
                   $output .= &Apache::lonhtmlcommon::confirm_success(
                        &mt('Comparison of student IDs: [_1] matching ([quant,_2,entry,entries])',
                               '<b>'.$showpct.'</b>',$found_match_count)).'<br />'.
                   &mt('Comparison of student IDs in the uploaded file with'.
                       ' the course roster found matches for [_1] of the [_2] entries'.
                       ' in the file (for the format defined for [_3]).',
                           '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs);
               } else {
                   # Not all entries matching? -> Show warning and additional info
                   $output .=
                       &Apache::lonhtmlcommon::confirm_success(
                           &mt('Comparison of student IDs: [_1] matching ([_2]/[quant,_3,entry,entries])',
                                   '<b>'.$showpct.'</b>',$found_match_count,$max_match_count).'<br />'.
                           &mt('Not all entries could be matched!'),1).'<br />'.
                       &mt('Comparison of student IDs in the uploaded file with'.
                           ' the course roster found matches for [_1] of the [_2] entries'.
                           ' in the file (for the format defined for [_3]).',
                               '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
                       '<p class="LC_info">'.
                       &mt('A low percentage of matches results from one of the following:').
                       '</p><ul>'.
                       '<li>'.&mt('The file was uploaded to the wrong course.').'</li>'.
                       '<li>'.&mt('The data is not in the format expected for the domain: [_1]',
                                  '<i>'.$cdom.'</i>').'</li>'.
                       '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
                       '<li>'.&mt('The course roster is not up to date.').'</li>'.
                       '</ul>';
               }
           }
       } else {
           $output = '<p class="LC_warning">'.&mt('Uploaded file contained no data').'</p>';
       }
       return $output;
   }
   
 sub valid_file {  sub valid_file {
     my ($requested_file)=@_;      my ($requested_file)=@_;
Line 7115  sub valid_file { Line 9367  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,$symb)=@_;
     my $default_form_data=&defaultFormData(&get_symb($r,1));      my $default_form_data=&defaultFormData($symb);
     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'};
     my $file=$env{'form.scantron_selectfile'};      my $file=$env{'form.scantron_selectfile'};
     if (! &valid_file($file)) {      if (! &valid_file($file)) {
  $r->print(<<ERROR);   $r->print('
  <p>   <p>
     The requested file name was invalid.      '.&mt('The requested filename was invalid.').'
         </p>          </p>
 ERROR  ');
  $r->print(&show_grading_menu_form(&get_symb($r,1)));  
  return;   return;
     }      }
     my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;      my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
Line 7146  ERROR Line 9387  ERROR
     &Apache::lonnet::allowuploaded('/adm/grades',$orig);      &Apache::lonnet::allowuploaded('/adm/grades',$orig);
     &Apache::lonnet::allowuploaded('/adm/grades',$corrected);      &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
     &Apache::lonnet::allowuploaded('/adm/grades',$skipped);      &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
     $r->print(<<DOWNLOAD);      $r->print('
     <p>      <p>
  <a href="$orig">Original</a> file as uploaded by the scantron office.   '.&mt('[_1]Original[_2] file as uploaded by the bubblesheet scanning office.',
         '<a href="'.$orig.'">','</a>').'
     </p>      </p>
     <p>      <p>
  <a href="$corrected">Corrections</a>, a file of corrected records that were used in grading.   '.&mt('[_1]Corrections[_2], a file of corrected records that were used in grading.',
         '<a href="'.$corrected.'">','</a>').'
     </p>      </p>
     <p>      <p>
  <a href="$skipped">Skipped</a>, a file of records that were skipped.   '.&mt('[_1]Skipped[_2], a file of records that were skipped.',
         '<a href="'.$skipped.'">','</a>').'
     </p>      </p>
 DOWNLOAD  ');
     $r->print(&show_grading_menu_form(&get_symb($r,1)));  
     return '';      return '';
 }  }
   
 =pod  sub checkscantron_results {
       my ($r,$symb) = @_;
       if (!$symb) {return '';}
       my $cid = $env{'request.course.id'};
       my %lettdig = &Apache::lonnet::letter_to_digits();
       my $numletts = scalar(keys(%lettdig));
       my $cnum = $env{'course.'.$cid.'.num'};
       my $cdom = $env{'course.'.$cid.'.domain'};
       my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
       my %record;
       my %scantron_config =
           &Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
       my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
       my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&Apache::grades::username_to_idmap($classlist);
       my $navmap=Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $r->print(&navmap_errormsg());
           return '';
       }
       my $map=$navmap->getResourceByUrl($sequence);
       my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
           %grader_randomlists_by_symb,%orderedforcode);
       if (ref($map)) { 
           $randomorder=$map->randomorder();
           $randompick=$map->randompick();
       }
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
       my $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
       if ($nav_error) {
           $r->print(&navmap_errormsg());
           return '';
       }
       &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                               \%grader_randomlists_by_symb,$bubbles_per_row);
       my ($uname,$udom);
       my (%scandata,%lastname,%bylast);
       $r->print('
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");
   
 =back      my @delayqueue;
       my %completedstudents;
   
       my $count=&get_todo_count($scanlines,$scan_data);
       my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
       my ($username,$domain,$started);
       &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
       if ($nav_error) {
           $r->print(&navmap_errormsg());
           return '';
       }
   
       &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
       my $start=&Time::HiRes::time();
       my $i=-1;
   
       while ($i<$scanlines->{'count'}) {
           ($username,$domain,$uname)=('','','');
           $i++;
           my $line=&Apache::grades::scantron_get_line($scanlines,$scan_data,$i);
           if ($line=~/^[\s\cz]*$/) { next; }
           if ($started) {
               &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
           }
           $started=1;
           my $scan_record=
               &Apache::grades::scantron_parse_scanline($line,$i,\%scantron_config,
                                                        $scan_data);
           unless ($uname=&scantron_find_student($scan_record,$scan_data,
                                                 \%idmap,$i)) {
               &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                                   'Unable to find a student that matches',1);
               next;
           }
           if (exists $completedstudents{$uname}) {
               &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                                   'Student '.$uname.' has multiple sheets',2);
               next;
           }
           my $pid = $scan_record->{'scantron.ID'};
           $lastname{$pid} = $scan_record->{'scantron.LastName'};
           push(@{$bylast{$lastname{$pid}}},$pid);
           my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
           my $user = $uname.':'.$usec;
           ($username,$domain)=split(/:/,$uname);
   
           my $scancode;
           if ((exists($scan_record->{'scantron.CODE'})) &&
               (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
               $scancode = $scan_record->{'scantron.CODE'};
           } else {
               $scancode = '';
           }
   
           my @mapresources = @resources;
           my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
           my %respnumlookup=();
           my %startline=();
           if ($randomorder || $randompick) {
               @mapresources =
                   &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
                                \%orderedforcode);
               my $total = &get_respnum_lookups($sequence,$scan_data,\%idmap,$line,
                                                $scan_record,\@master_seq,\%symb_to_resource,
                                                \%grader_partids_by_symb,\%orderedforcode,
                                                \%respnumlookup,\%startline);
               if ($randompick && $total) {
                   $lastpos = $total*$scantron_config{'Qlength'};
               }
           }
           $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
           chomp($scandata{$pid});
           $scandata{$pid} =~ s/\r$//;
   
           my $counter = -1;
           foreach my $resource (@mapresources) {
               my $parts;
               my $ressymb = $resource->symb();
               if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
                   my $currcode;
                   if (exists($grader_randomlists_by_symb{$ressymb})) {
                       $currcode = $scancode;
                   }
                   (my $analysis,$parts) =
                       &scantron_partids_tograde($resource,$env{'request.course.id'},
                                                 $username,$domain,undef,
                                                 $bubbles_per_row,$currcode);
               } else {
                   $parts = $grader_partids_by_symb{$ressymb};
               }
               ($counter,my $recording) =
                   &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
                                            $scandata{$pid},$parts,
                                            \%scantron_config,\%lettdig,$numletts,
                                            $randomorder,$randompick,
                                            \%respnumlookup,\%startline);
               $record{$pid} .= $recording;
           }
       }
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       $r->print('<br />');
       my ($okstudents,$badstudents,$numstudents,$passed,$failed);
       $passed = 0;
       $failed = 0;
       $numstudents = 0;
       foreach my $last (sort(keys(%bylast))) {
           if (ref($bylast{$last}) eq 'ARRAY') {
               foreach my $pid (sort(@{$bylast{$last}})) {
                   my $showscandata = $scandata{$pid};
                   my $showrecord = $record{$pid};
                   $showscandata =~ s/\s/&nbsp;/g;
                   $showrecord =~ s/\s/&nbsp;/g;
                   if ($scandata{$pid} eq $record{$pid}) {
                       my $css_class = ($passed % 2)?'LC_odd_row':'LC_even_row';
                       $okstudents .= '<tr class="'.$css_class.'">'.
   '<td>'.&mt('Bubblesheet').'</td><td>'.$showscandata.'</td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
   '</tr>'."\n".
   '<tr class="'.$css_class.'">'."\n".
   '<td>'.&mt('Submissions').'</td><td>'.$showrecord.'</td></tr>'."\n";
                       $passed ++;
                   } else {
                       my $css_class = ($failed % 2)?'LC_odd_row':'LC_even_row';
                       $badstudents .= '<tr class="'.$css_class.'"><td>'.&mt('Bubblesheet').'</td><td><span class="LC_nobreak">'.$scandata{$pid}.'</span></td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
   '</tr>'."\n".
   '<tr class="'.$css_class.'">'."\n".
   '<td>'.&mt('Submissions').'</td><td><span class="LC_nobreak">'.$record{$pid}.'</span></td>'."\n".
   '</tr>'."\n";
                       $failed ++;
                   }
                   $numstudents ++;
               }
           }
       }
       $r->print(
           '<p>'
          .&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for [_1][quant,_2,student][_3] ([quant,_4,bubblesheet line] per student).',
               '<b>',
               $numstudents,
               '</b>',
               $env{'form.scantron_maxbubble'})
          .'</p>'
       );
       $r->print('<p>'
                .&mt('Exact matches for [_1][quant,_2,student][_3].','<b>',$passed,'</b>')
                .'<br />'
                .&mt('Discrepancies detected for [_1][quant,_2,student][_3].','<b>',$failed,'</b>')
                .'</p>'
       );
       if ($passed) {
           $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').'<br /><br />');
           $r->print(&Apache::loncommon::start_data_table()."\n".
                    &Apache::loncommon::start_data_table_header_row()."\n".
                    '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                    &Apache::loncommon::end_data_table_header_row()."\n".
                    $okstudents."\n".
                    &Apache::loncommon::end_data_table().'<br />');
       }
       if ($failed) {
           $r->print(&mt('Students with differences between bubblesheet data and submissions are as follows:').'<br /><br />');
           $r->print(&Apache::loncommon::start_data_table()."\n".
                    &Apache::loncommon::start_data_table_header_row()."\n".
                    '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                    &Apache::loncommon::end_data_table_header_row()."\n".
                    $badstudents."\n".
                    &Apache::loncommon::end_data_table()).'<br />'.
                    &mt('Differences can occur if submissions were modified using manual grading after a bubblesheet grading pass.').'<br />'.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original bubblesheets.');  
       }
       $r->print('</form><br />');
       return;
   }
   
   sub verify_scantron_grading {
       my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
           $scantron_config,$lettdig,$numletts,$randomorder,$randompick,
           $respnumlookup,$startline) = @_;
       my ($record,%expected,%startpos);
       return ($counter,$record) if (!ref($resource));
       return ($counter,$record) if (!$resource->is_problem());
       my $symb = $resource->symb();
       return ($counter,$record) if (ref($partids) ne 'ARRAY');
       foreach my $part_id (@{$partids}) {
           $counter ++;
           $expected{$part_id} = 0;
           my $respnum = $counter;
           if ($randomorder || $randompick) {
               $respnum = $respnumlookup->{$counter};
               $startpos{$part_id} = $startline->{$counter} + 1;
           } else {
               $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
           }
           if ($env{"form.scantron.sub_bubblelines.$respnum"}) {
               my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$respnum"});
               foreach my $item (@sub_lines) {
                   $expected{$part_id} += $item;
               }
           } else {
               $expected{$part_id} = $env{"form.scantron.bubblelines.$respnum"};
           }
       }
       if ($symb) {
           my %recorded;
           my (%returnhash) = &Apache::lonnet::restore($symb,$cid,$domain,$username);
           if ($returnhash{'version'}) {
               my %lasthash=();
               my $version;
               for ($version=1;$version<=$returnhash{'version'};$version++) {
                   foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
                       $lasthash{$key}=$returnhash{$version.':'.$key};
                   }
               }
               foreach my $key (keys(%lasthash)) {
                   if ($key =~ /\.scantron$/) {
                       my $value = &unescape($lasthash{$key});
                       my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/);
                       if ($value eq '') {
                           for (my $i=0; $i<$expected{$part_id}; $i++) {
                               for (my $j=0; $j<$scantron_config->{'length'}; $j++) {
                                   $recorded{$part_id} .= $scantron_config->{'Qoff'};
                               }
                           }
                       } else {
                           my @tocheck;
                           my @items = split(//,$value);
                           if (($scantron_config->{'Qon'} eq 'letter') ||
                               ($scantron_config->{'Qon'} eq 'number')) {
                               if (@items < $expected{$part_id}) {
                                   my $fragment = substr($scandata,$startpos{$part_id},$expected{$part_id});
                                   my @singles = split(//,$fragment);
                                   foreach my $pos (@singles) {
                                       if ($pos eq ' ') {
                                           push(@tocheck,$pos);
                                       } else {
                                           my $next = shift(@items);
                                           push(@tocheck,$next);
                                       }
                                   }
                               } else {
                                   @tocheck = @items;
                               }
                               foreach my $letter (@tocheck) {
                                   if ($scantron_config->{'Qon'} eq 'letter') {
                                       if ($letter !~ /^[A-J]$/) {
                                           $letter = $scantron_config->{'Qoff'};
                                       }
                                       $recorded{$part_id} .= $letter;
                                   } elsif ($scantron_config->{'Qon'} eq 'number') {
                                       my $digit;
                                       if ($letter !~ /^[A-J]$/) {
                                           $digit = $scantron_config->{'Qoff'};
                                       } else {
                                           $digit = $lettdig->{$letter};
                                       }
                                       $recorded{$part_id} .= $digit;
                                   }
                               }
                           } else {
                               @tocheck = @items;
                               for (my $i=0; $i<$expected{$part_id}; $i++) {
                                   my $curr_sub = shift(@tocheck);
                                   my $digit;
                                   if ($curr_sub =~ /^[A-J]$/) {
                                       $digit = $lettdig->{$curr_sub}-1;
                                   }
                                   if ($curr_sub eq 'J') {
                                       $digit += scalar($numletts);
                                   }
                                   for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                                       if ($j == $digit) {
                                           $recorded{$part_id} .= $scantron_config->{'Qon'};
                                       } else {
                                           $recorded{$part_id} .= $scantron_config->{'Qoff'};
                                       }
                                   }
                               }
                           }
                       }
                   }
               }
           }
           foreach my $part_id (@{$partids}) {
               if ($recorded{$part_id} eq '') {
                   for (my $i=0; $i<$expected{$part_id}; $i++) {
                       for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                           $recorded{$part_id} .= $scantron_config->{'Qoff'};
                       }
                   }
               }
               $record .= $recorded{$part_id};
           }
       }
       return ($counter,$record);
   }
   
 =cut  
   
 #-------- end of section for handling grading scantron forms -------  #-------- end of section for handling grading scantron forms -------
 #  #
Line 7173  DOWNLOAD Line 9746  DOWNLOAD
   
 #-------------------------- Menu interface -------------------------  #-------------------------- Menu interface -------------------------
 #  #
 #--- Show a Grading Menu button - Calls the next routine ---  #--- Href with symb and command ---
 sub show_grading_menu_form {  
     my ($symb)=@_;  
     my $result.='<br /><form action="/adm/grades" method="post">'."\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="command" value="gradingmenu" />'."\n".  
  '<input type="submit" name="submit" value="Grading Menu" />'."\n".  
  '</form>'."\n";  
     return $result;  
 }  
   
 # -- Retrieve choices for grading form  sub href_symb_cmd {
 sub savedState {      my ($symb,$cmd)=@_;
     my %savedState = ();      return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&amp;command='.$cmd;
     if ($env{'form.saveState'}) {  
  foreach (split(/:/,$env{'form.saveState'})) {  
     my ($key,$value) = split(/=/,$_,2);  
     $savedState{$key} = $value;  
  }  
     }  
     return \%savedState;  
 }  }
   
 sub grading_menu {  sub grading_menu {
     my ($request) = @_;      my ($request,$symb) = @_;
     my ($symb)=&get_symb($request);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $probTitle = &Apache::lonnet::gettitle($symb);  
     my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);  
   
     $request->print($table);  
     my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),      my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
                   'handgrade'=>$hdgrade,                    'command'=>'individual');
                   'probTitle'=>$probTitle,      
                   'command'=>'submit_options',      my $url1a = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   'saveState'=>"",  
                   'gradingMenu'=>1,      $fields{'command'}='ungraded';
                   'showgrading'=>"yes");      my $url1b=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
     my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);  
     my @menu = ({ url => $url,      $fields{'command'}='table';
                      name => &mt('Manual Grading/View Submissions'),      my $url1c=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
                      short_description =>   
     &mt('Start the process of hand grading submissions.'),      $fields{'command'}='all_for_one';
                  });      my $url1d=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
   
       $fields{'command'}='downloadfilesselect';
       my $url1e=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
   
     $fields{'command'} = 'csvform';      $fields{'command'} = 'csvform';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,      
                    name => &mt('Upload Scores'),  
                    short_description =>   
             &mt('Specify a file containing the class scores for current resource.')});  
     $fields{'command'} = 'processclicker';      $fields{'command'} = 'processclicker';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      my $url3 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,      
                    name => &mt('Process Clicker'),  
                    short_description =>   
             &mt('Specify a file containing the clicker information for this resource.')});  
     $fields{'command'} = 'scantron_selectphase';      $fields{'command'} = 'scantron_selectphase';
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);      my $url4 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
     push (@menu, { url => $url,  
                    name => &mt('Grade/Manage Scantron Forms'),      $fields{'command'} = 'initialverifyreceipt';
                    short_description =>       my $url5 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
             &mt('')});      
     $fields{'command'} = 'verify';      my @menu = ({ categorytitle=>'Hand Grading',
     $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);              items =>[
     push (@menu, { url => "",                          { linktext => 'Select individual students to grade',
                    name => &mt('Verify Receipt'),                      url => $url1a,
                    short_description =>                       permission => 'F',
             &mt('')});                      icon => 'grade_students.png',
     #                      linktitle => 'Grade current resource for a selection of students.'
                           }, 
                           {       linktext => 'Grade ungraded submissions',
                                   url => $url1b,
                                   permission => 'F',
                                   icon => 'ungrade_sub.png',
                                   linktitle => 'Grade all submissions that have not been graded yet.'
                           },
   
                           {       linktext => 'Grading table',
                                   url => $url1c,
                                   permission => 'F',
                                   icon => 'grading_table.png',
                                   linktitle => 'Grade current resource for all students.'
                           },
                           {       linktext => 'Grade page/folder for one student',
                                   url => $url1d,
                                   permission => 'F',
                                   icon => 'grade_PageFolder.png',
                                   linktitle => 'Grade all resources in current page/sequence/folder for one student.'
                           },
                           {       linktext => 'Download submissions',
                                   url => $url1e,
                                   permission => 'F',
                                   icon => 'download_sub.png',
                                   linktitle => 'Download all students submissions.'
                           }]},
                            { categorytitle=>'Automated Grading',
                  items =>[
   
                      { linktext => 'Upload Scores',
                       url => $url2,
                       permission => 'F',
                       icon => 'uploadscores.png',
                       linktitle => 'Specify a file containing the class scores for current resource.'
                      },
                      { linktext => 'Process Clicker',
                       url => $url3,
                       permission => 'F',
                       icon => 'addClickerInfoFile.png',
                       linktitle => 'Specify a file containing the clicker information for this resource.'
                      },
                      { linktext => 'Grade/Manage/Review Bubblesheets',
                       url => $url4,
                       permission => 'F',
                       icon => 'bubblesheet.png',
                       linktitle => 'Grade bubblesheet exams, upload/download bubblesheet data files, and review previously graded bubblesheet exams.'
                      },
                               {   linktext => 'Verify Receipt Number',
                                   url => $url5,
                                   permission => 'F',
                                   icon => 'receipt_number.png',
                                   linktitle => 'Verify a system-generated receipt number for correct problem solution.'
                               }
   
                       ]
               });
   
     # Create the menu      # Create the menu
     my $Str;      my $Str;
     # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>';  
     $Str .= '<form method="post" action="" name="gradingMenu">';      $Str .= '<form method="post" action="" name="gradingMenu">';
     $Str .= '<input type="hidden" name="command" value="" />'.      $Str .= '<input type="hidden" name="command" value="" />'.
     '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".      '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
  '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".  
  '<input type="hidden" name="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) {      $Str .= &Apache::lonhtmlcommon::generate_menu(@menu);
  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;          return $Str;    
 }  }
   
   
   sub ungraded {
       my ($request)=@_;
       &submit_options($request);
   }
   
   sub submit_options_sequence {
       my ($request,$symb) = @_;
       if (!$symb) {return '';}
       &commonJSfunctions($request);
       my $result;
   
       $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
           '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
       $result.=&selectfield(0).
               '<input type="hidden" name="command" value="pickStudentPage" />
               <div>
                 <input type="submit" value="'.&mt('Next').' &rarr;" />
               </div>
           </div>
     </form>';
       return $result;
   }
   
   sub submit_options_table {
       my ($request,$symb) = @_;
       if (!$symb) {return '';}
       &commonJSfunctions($request);
       my $is_tool = ($symb =~ /ext\.tool$/);
       my $result;
   
       $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
           '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
   
       $result.=&selectfield(1,$is_tool).
               '<input type="hidden" name="command" value="viewgrades" />
               <div>
                 <input type="submit" value="'.&mt('Next').' &rarr;" />
               </div>
           </div>
     </form>';
       return $result;
   }
   
   sub submit_options_download {
       my ($request,$symb) = @_;
       if (!$symb) {return '';}
   
       my $is_tool = ($symb =~ /ext\.tool$/);
       &commonJSfunctions($request);
   
       my $result='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
           '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
       $result.='
   <h2>
     '.&mt('Select Students for whom to Download Submissions').'
   </h2>'.&selectfield(1,$is_tool).'
                   <input type="hidden" name="command" value="downloadfileslink" /> 
                 <input type="submit" value="'.&mt('Next').' &rarr;" />
               </div>
             </div>
   
   
     </form>';
       return $result;
   }
   
 #--- Displays the submissions first page -------  #--- Displays the submissions first page -------
 sub submit_options {  sub submit_options {
     my ($request) = @_;      my ($request,$symb) = @_;
     my ($symb)=&get_symb($request);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $probTitle = &Apache::lonnet::gettitle($symb);  
   
     $request->print(<<GRADINGMENUJS);      my $is_tool = ($symb =~ /ext\.tool$/);
 <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;  
  formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+  
     ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);  
  if (val < 5) formname.submit();  
  if (val == 5) {  
     if (!checkReceiptNo(formname,'notOK')) { return false;}  
     formname.submit();  
  }  
  if (val < 7) 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);      &commonJSfunctions($request);
     my $result='<h3>&nbsp;<span class="LC_info">Manual Grading/View Submission</span></h3>';      my $result;
     my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);  
     $result.=$table;  
     my (undef,$sections) = &getclasslist('all','0');  
     my $savedState = &savedState();  
     my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});  
     my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'});  
     my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});  
     my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});  
   
     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".      $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
  '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".   '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
  '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".      $result.=&selectfield(1,$is_tool).'
  '<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".                  <input type="hidden" name="command" value="submission" /> 
  '<input type="hidden" name="command"     value="" />'."\n".        <input type="submit" value="'.&mt('Next').' &rarr;" />
  '<input type="hidden" name="saveState"   value="" />'."\n".              </div>
  '<input type="hidden" name="gradingMenu" value="1" />'."\n".            </div>
  '<input type="hidden" name="showgrading" value="yes" />'."\n";    </form>';
       return $result;
     $result.='<table border="0"><tr><td bgcolor=#777777>'."\n".  }
  '<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n".  
  '&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".  sub selectfield {
  '<tr bgcolor="#ffffe6" valign="top"><td>'."\n";     my ($full,$is_tool)=@_;
      my %options;
     $result.='<table width="100%" border="0">';     if ($is_tool) {
     $result.='<tr bgcolor="#ffffe6" valign="top">'."\n";         %options =
     $result.='<td><b>'.&mt('Sections').'</b></td>';             (&transtatus_options,
     $result.='<td><b>'.&mt('Groups').'</b></td>';              'select_form_order' => ['yes','incorrect','all']);
     $result.='<td><b>'.&mt('Access Status').'</td>'."\n";     } else {
     $result.='<td><b>'.&mt('Submission Status').'</td>'."\n";         %options = 
     $result.='</tr>';             (&substatus_options,
     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".              'select_form_order' => ['yes','queued','graded','incorrect','all']);
  '&nbsp;<select name="section" multiple="multiple" size="3">'."\n";     }
     if (ref($sections)) {     my $result='<div class="LC_columnSection">
  foreach (sort (@$sections)) {    
     $result.='<option value="'.$_.'" '.      <fieldset>
  ($saveSec eq $_ ? 'selected="selected"':'').'>'.$_.'</option>'."\n";        <legend>
  }         '.&mt('Sections').'
     }        </legend>
     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';        '.&Apache::lonstatistics::SectionSelect('section','multiple',5).'
     $result.= '</td><td>'."\n";      </fieldset>
     $result.= &Apache::lonstatistics::GroupSelect('group','multiple',3);    
     $result.='</td><td>'."\n";      <fieldset>
     $result.=&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,3,undef,'mult');        <legend>
           '.&mt('Groups').'
     $result.='</td>';        </legend>
     $result.='<td><select name="submitonly" size="3">'.        '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
  '<option value="yes" '.      </fieldset>
  ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>'.    
  '<option value="queued" '.      <fieldset>
  ($saveSub eq 'queued' ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>'.        <legend>
  '<option value="graded" '.          '.&mt('Access Status').'
  ($saveSub eq 'graded' ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>'.        </legend>
  '<option value="incorrect" '.        '.&Apache::lonhtmlcommon::StatusOptions(undef,undef,5,undef,'mult').'
  ($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>'.      </fieldset>';
  '<option value="all" '.      if ($full) {
  ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>';          my $heading = &mt('Submission Status');
           if ($is_tool) {
     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3"><label>'.              $heading = &mt('Transaction Status');
  '<input type="radio" name="radioChoice" value="submission" '.          }
  ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').          $result.='
  '</label> </td></tr>'."\n";      <fieldset>
         <legend>
     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3">'.          '.$heading.'
  '<label><input type="radio" name="radioChoice" value="viewgrades" '.        </legend>'.
  ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.         &Apache::loncommon::select_form('all','submitonly',\%options).
  '<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n";     '</fieldset>';
       }
     $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'.      $result.='</div><br />';
  '<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"' : '').' /> '.  
  'The <b>complete</b> set/page/sequence/folder: For one student</label></td></tr>'."\n";  
   
     $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'.  
  '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.  
  '</td></tr></table>'."\n";  
   
     $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".'</td>';  
     $result.= '</tr></table>'."\n".  
  '</td></tr></table></form>'."\n";  
     return $result;      return $result;
 }  }
   
   sub substatus_options {
       return &Apache::lonlocal::texthash(
                                         'yes'       => 'with submissions',
                                         'queued'    => 'in grading queue',
                                         'graded'    => 'with ungraded submissions',
                                         'incorrect' => 'with incorrect submissions',
                                         'all'       => 'with any status',
                                         );
   }
   
   sub transtatus_options {
       return &Apache::lonlocal::texthash(
                                          'yes'       => 'with score transactions',
                                          'incorrect' => 'with less than full credit',
                                          'all'       => 'with any status',
                                         );
   }
   
 sub reset_perm {  sub reset_perm {
     undef(%perm);      undef(%perm);
 }  }
Line 7501  sub init_perm { Line 10038  sub init_perm {
     }      }
 }  }
   
   sub init_old_essays {
       my ($symb,$apath,$adom,$aname) = @_;
       if ($symb ne '') {
           my %essays = &Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
           if (keys(%essays) > 0) {
               $old_essays{$symb} = \%essays;
           }
       }
       return;
   }
   
   sub reset_old_essays {
       undef(%old_essays);
   }
   
 sub gather_clicker_ids {  sub gather_clicker_ids {
     my %clicker_ids;      my %clicker_ids;
   
Line 7563  sub clicker_grading_parameters { Line 10115  sub clicker_grading_parameters {
 }  }
   
 sub process_clicker {  sub process_clicker {
     my ($r)=@_;      my ($r,$symb)=@_;
     my ($symb)=&get_symb($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $result=&checkforfile_js();      my $result=&checkforfile_js();
     $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);      $result.=&Apache::loncommon::start_data_table().
     my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});               &Apache::loncommon::start_data_table_header_row().
     $result.=$table;               '<th>'.&mt('Specify a file containing clicker information and set grading options.').'</th>'.
     $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";               &Apache::loncommon::end_data_table_header_row().
     $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";               &Apache::loncommon::start_data_table_row()."<td>\n";
     $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource').  
         '.</b></td></tr>'."\n";  
     $result.='<tr bgcolor=#ffffe6><td>'."\n";  
 # Attempt to restore parameters from last session, set defaults if not present  # Attempt to restore parameters from last session, set defaults if not present
     my %Saveable_Parameters=&clicker_grading_parameters();      my %Saveable_Parameters=&clicker_grading_parameters();
     &Apache::loncommon::restore_course_settings('grades_clicker',      &Apache::loncommon::restore_course_settings('grades_clicker',
Line 7585  sub process_clicker { Line 10133  sub process_clicker {
     if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }      if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
   
     my %checked;      my %checked;
     foreach my $gradingmechanism ('attendance','personnel','specific') {      foreach my $gradingmechanism ('attendance','personnel','specific','given') {
        if ($env{'form.gradingmechanism'} eq $gradingmechanism) {         if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
           $checked{$gradingmechanism}="checked='checked'";            $checked{$gradingmechanism}=' checked="checked"';
        }         }
     }      }
   
     my $upload=&mt("Upload File");      my $upload=&mt("Evaluate File");
     my $type=&mt("Type");      my $type=&mt("Type");
     my $attendance=&mt("Award points just for participation");      my $attendance=&mt("Award points just for participation");
     my $personnel=&mt("Correctness determined from response by course personnel");      my $personnel=&mt("Correctness determined from response by course personnel");
     my $specific=&mt("Correctness determined from response with clicker ID(s)");       my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
       my $given=&mt("Correctness determined from given list of answers").' '.
                 '<font size="-2"><tt>('.&mt("Provide comma-separated list. Use '*' for any answer correct, '-' for skip").')</tt></font>';
     my $pcorrect=&mt("Percentage points for correct solution");      my $pcorrect=&mt("Percentage points for correct solution");
     my $pincorrect=&mt("Percentage points for incorrect solution");      my $pincorrect=&mt("Percentage points for incorrect solution");
     my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',      my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
    ('iclicker' => 'i>clicker',     {'iclicker' => 'i>clicker',
                                                     'interwrite' => 'interwrite PRS'));                                                      'interwrite' => 'interwrite PRS',
                                                       'turning' => 'Turning Technologies'});
     $symb = &Apache::lonenc::check_encrypt($symb);      $symb = &Apache::lonenc::check_encrypt($symb);
     $result.=<<ENDUPFORM;      $result.= &Apache::lonhtmlcommon::scripttag(<<ENDUPFORM);
 <script type="text/javascript">  
 function sanitycheck() {  function sanitycheck() {
 // Accept only integer percentages  // Accept only integer percentages
    document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);     document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
Line 7641  function sanitycheck() { Line 10191  function sanitycheck() {
 // Remember the old state  // Remember the old state
    document.forms.gradesupload.waschecked.value=newgradingchoice;     document.forms.gradesupload.waschecked.value=newgradingchoice;
 }  }
 </script>  ENDUPFORM
       $result.= <<ENDUPFORM;
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <input type="hidden" name="symb" value="$symb" />  <input type="hidden" name="symb" value="$symb" />
 <input type="hidden" name="command" value="processclickerfile" />  <input type="hidden" name="command" value="processclickerfile" />
 <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />  
 <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />  
 <input type="file" name="upfile" size="50" />  <input type="file" name="upfile" size="50" />
 <br /><label>$type: $selectform</label>  <br /><label>$type: $selectform</label>
 <br /><label><input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" />$attendance </label>  ENDUPFORM
 <br /><label><input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" />$personnel</label>      $result.='</td>'.&Apache::loncommon::end_data_table_row().
 <br /><label><input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" />$specific </label>                       &Apache::loncommon::start_data_table_row().'<td>'.(<<ENDGRADINGFORM);
         <label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onclick="sanitycheck()" />$attendance </label>
   <br /><label><input type="radio" name="gradingmechanism" value="personnel"$checked{'personnel'} onclick="sanitycheck()" />$personnel</label>
   <br /><label><input type="radio" name="gradingmechanism" value="specific"$checked{'specific'} onclick="sanitycheck()" />$specific </label>
 <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />  <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
   <br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onclick="sanitycheck()" />$given </label>
   <br />&nbsp;&nbsp;&nbsp;
   <input type="text" name="givenanswer" size="50" />
 <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />  <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
 <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>  ENDGRADINGFORM
 <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>      $result.='</td>'.&Apache::loncommon::end_data_table_row().
 <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />                       &Apache::loncommon::start_data_table_row().'<td>'.(<<ENDPERCFORM);
 </form>        <label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onchange="sanitycheck()" /></label>
 ENDUPFORM  <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onchange="sanitycheck()" /></label>
     $result.='</td></tr></table>'."\n".  <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
              '</td></tr></table><br /><br />'."\n";  </form>'
     $result.=&show_grading_menu_form($symb);  ENDPERCFORM
       $result.='</td>'.
                &Apache::loncommon::end_data_table_row().
                &Apache::loncommon::end_data_table();
     return $result;      return $result;
 }  }
   
 sub process_clicker_file {  sub process_clicker_file {
     my ($r)=@_;      my ($r,$symb) = @_;
     my ($symb)=&get_symb($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
   
     my %Saveable_Parameters=&clicker_grading_parameters();      my %Saveable_Parameters=&clicker_grading_parameters();
     &Apache::loncommon::store_course_settings('grades_clicker',      &Apache::loncommon::store_course_settings('grades_clicker',
                                               \%Saveable_Parameters);                                                \%Saveable_Parameters);
       my $result='';
     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});  
     if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {      if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
  $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';   $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
  return $result.&show_grading_menu_form($symb);   return $result;
       }
       if (($env{'form.gradingmechanism'} eq 'given') && ($env{'form.givenanswer'}!~/\S/)) {
           $result.='<span class="LC_error">'.&mt('You need to specify the correct answer').'</span>';
           return $result;
       }
       my $foundgiven=0;
       if ($env{'form.gradingmechanism'} eq 'given') {
           $env{'form.givenanswer'}=~s/^\s*//gs;
           $env{'form.givenanswer'}=~s/\s*$//gs;
           $env{'form.givenanswer'}=~s/[^a-zA-Z0-9\.\*\-\+]+/\,/g;
           $env{'form.givenanswer'}=uc($env{'form.givenanswer'});
           my @answers=split(/\,/,$env{'form.givenanswer'});
           $foundgiven=$#answers+1;
     }      }
     my %clicker_ids=&gather_clicker_ids();      my %clicker_ids=&gather_clicker_ids();
     my %correct_ids;      my %correct_ids;
Line 7697  sub process_clicker_file { Line 10266  sub process_clicker_file {
     }      }
     if ($env{'form.gradingmechanism'} eq 'attendance') {      if ($env{'form.gradingmechanism'} eq 'attendance') {
  $result.=&mt('Score based on attendance only');   $result.=&mt('Score based on attendance only');
       } elsif ($env{'form.gradingmechanism'} eq 'given') {
           $result.=&mt('Score based on [_1] ([_2] answers)','<tt>'.$env{'form.givenanswer'}.'</tt>',$foundgiven);
     } else {      } else {
  my $number=0;   my $number=0;
  $result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';   $result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
Line 7711  sub process_clicker_file { Line 10282  sub process_clicker_file {
     $number++;      $number++;
  }   }
         $result.="</p>\n";          $result.="</p>\n";
  if ($number==0) {          if ($number==0) {
     $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';              $result .=
     return $result.&show_grading_menu_form($symb);                   &Apache::lonhtmlcommon::confirm_success(
  }                       &mt('No IDs found to determine correct answer'),1);
               return $result;
           }
     }      }
     if (length($env{'form.upfile'}) < 2) {      if (length($env{'form.upfile'}) < 2) {
         $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.',          $result .=
      '<span class="LC_error">',              &Apache::lonhtmlcommon::confirm_success(
      '</span>',                  &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
      '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');                          '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1);
         return $result.&show_grading_menu_form($symb);          return $result;
       }
       my $mimetype;
       if ($env{'form.upfiletype'} eq 'iclicker') {
           my $mm = new File::MMagic;
           $mimetype = $mm->checktype_contents($env{'form.upfile'});
           unless (($mimetype eq 'text/plain') || ($mimetype eq 'text/html')) {
               $result.= '<p>'.
                   &Apache::lonhtmlcommon::confirm_success(
                       &mt('File format is neither csv (iclicker 6) nor xml (iclicker 7)'),1).'</p>';
               return $result;
           }
       } elsif (($env{'form.upfiletype'} ne 'interwrite') && ($env{'form.upfiletype'} ne 'turning')) {
           $result .= '<p>'.
               &Apache::lonhtmlcommon::confirm_success(
                   &mt('Invalid clicker type: choose one of: i>clicker, Interwrite PRS, or Turning Technologies.'),1).'</p>';
           return $result;
     }      }
   
 # Were able to get all the info needed, now analyze the file  # Were able to get all the info needed, now analyze the file
   
     $result.=&Apache::loncommon::studentbrowser_javascript();      $result.=&Apache::loncommon::studentbrowser_javascript();
     $symb = &Apache::lonenc::check_encrypt($symb);      $symb = &Apache::lonenc::check_encrypt($symb);
     my $heading=&mt('Scanning clicker file');      $result.=&Apache::loncommon::start_data_table().
     $result.=(<<ENDHEADER);               &Apache::loncommon::start_data_table_header_row().
 <br /><table width="100%" border="0"><tr><td bgcolor="#777777">               '<th>'.&mt('Evaluate clicker file').'</th>'.
 <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>               &Apache::loncommon::end_data_table_header_row().
 <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>               &Apache::loncommon::start_data_table_row().(<<ENDHEADER);
   <td>
 <form method="post" action="/adm/grades" name="clickeranalysis">  <form method="post" action="/adm/grades" name="clickeranalysis">
 <input type="hidden" name="symb" value="$symb" />  <input type="hidden" name="symb" value="$symb" />
 <input type="hidden" name="command" value="assignclickergrades" />  <input type="hidden" name="command" value="assignclickergrades" />
 <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />  
 <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />  
 <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />  <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
 <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />  <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
 <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />  <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
 ENDHEADER  ENDHEADER
       if ($env{'form.gradingmechanism'} eq 'given') {
          $result.='<input type="hidden" name="correct:given" value="'.$env{'form.givenanswer'}.'" />';
       } 
     my %responses;      my %responses;
     my @questiontitles;      my @questiontitles;
     my $errormsg='';      my $errormsg='';
     my $number=0;      my $number=0;
     if ($env{'form.upfiletype'} eq 'iclicker') {      if ($env{'form.upfiletype'} eq 'iclicker') {
  ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);          if ($mimetype eq 'text/plain') {
     }              ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
     if ($env{'form.upfiletype'} eq 'interwrite') {          } elsif ($mimetype eq 'text/html') {
               ($errormsg,$number)=&iclickerxml_eval(\@questiontitles,\%responses);
           }
       } elsif ($env{'form.upfiletype'} eq 'interwrite') {
         ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);          ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
       } elsif ($env{'form.upfiletype'} eq 'turning') {
           ($errormsg,$number)=&turning_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 />';
       if (($env{'form.gradingmechanism'} eq 'given') && ($number!=$foundgiven)) {
          $result.='<span class="LC_error">'.&mt('Number of given answers does not agree with number of questions in file.').'</span>';
          return $result;
       } 
 # Remember Question Titles  # Remember Question Titles
 # FIXME: Possibly need delimiter other than ":"  # FIXME: Possibly need delimiter other than ":"
     for (my $i=0;$i<$number;$i++) {      for (my $i=0;$i<$number;$i++) {
Line 7777  ENDHEADER Line 10375  ENDHEADER
        } elsif ($clicker_ids{$id}) {         } elsif ($clicker_ids{$id}) {
           if ($clicker_ids{$id}=~/\,/) {            if ($clicker_ids{$id}=~/\,/) {
 # More than one user with the same clicker!  # More than one user with the same clicker!
              $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";               $result.="</td>".&Apache::loncommon::end_data_table_row().
                              &Apache::loncommon::start_data_table_row()."<td>".
                          &mt('Clicker registered more than once').": <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}.'" />'.
                            "<select name='multi".$id."'>";                             "<select name='multi".$id."'>";
              foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {               foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
Line 7791  ENDHEADER Line 10391  ENDHEADER
              $student_count++;               $student_count++;
           }            }
        } else {         } else {
           $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";            $result.="</td>".&Apache::loncommon::end_data_table_row().
                              &Apache::loncommon::start_data_table_row()."<td>".
                       &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}.'" />'.
                    "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".                     "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
                    "\n".&mt("Domain").": ".                     "\n".&mt("Domain").": ".
                    &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.                     &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
                    &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);                     &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id,'',$id);
           $unknown_count++;            $unknown_count++;
        }         }
     }      }
     $result.='<hr />'.      $result.='<hr />'.
              &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);               &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
     if ($env{'form.gradingmechanism'} ne 'attendance') {      if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
        if ($correct_count==0) {         if ($correct_count==0) {
           $errormsg.="Found no correct answers answers for grading!";            $errormsg.="Found no correct answers for grading!";
        } elsif ($correct_count>1) {         } elsif ($correct_count>1) {
           $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>';
        }         }
Line 7817  ENDHEADER Line 10419  ENDHEADER
     } else {      } else {
        $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';         $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
     }      }
     $result.='</form></td></tr></table>'."\n".      $result.='</form></td>'.
              '</td></tr></table><br /><br />'."\n";               &Apache::loncommon::end_data_table_row().
     return $result.&show_grading_menu_form($symb);               &Apache::loncommon::end_data_table();
       return $result;
 }  }
   
 sub iclicker_eval {  sub iclicker_eval {
Line 7841  sub iclicker_eval { Line 10444  sub iclicker_eval {
     $id=~s/^[\#0]+//;      $id=~s/^[\#0]+//;
     for (my $i=0;$i<$number;$i++) {      for (my $i=0;$i<$number;$i++) {
  my $idx=3+$i*6;   my $idx=3+$i*6;
                   $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+]+//g;
  push(@idresponses,$entries[$idx]);   push(@idresponses,$entries[$idx]);
     }      }
     $$responses{$id}=join(',',@idresponses);      $$responses{$id}=join(',',@idresponses);
Line 7849  sub iclicker_eval { Line 10453  sub iclicker_eval {
     return ($errormsg,$number);      return ($errormsg,$number);
 }  }
   
   sub iclickerxml_eval {
       my ($questiontitles,$responses)=@_;
       my $number=0;
       my $errormsg='';
       my @state;
       my %respbyid;
       my $p = HTML::Parser->new
       (
           xml_mode => 1,
           start_h =>
               [sub {
                    my ($tagname,$attr) = @_;
                    push(@state,$tagname);
                    if ("@state" eq "ssn p") {
                        my $title = $attr->{qn};
                        $title =~ s/(^\s+|\s+$)//g;
                        $questiontitles->[$number]=$title;
                    } elsif ("@state" eq "ssn p v") {
                        my $id = $attr->{id};
                        my $entry = $attr->{ans};
                        $id=~s/^[\#0]+//;
                        $entry =~s/[^a-zA-Z0-9\.\*\-\+]+//g;
                        $respbyid{$id}[$number] = $entry;
                    }
               }, "tagname, attr"],
            end_h =>
                  [sub {
                      my ($tagname) = @_;
                      if ("@state" eq "ssn p") {
                          $number++;
                      }
                      pop(@state);
                   }, "tagname"],
       );
   
       $p->parse($env{'form.upfile'});
       $p->eof;
       foreach my $id (keys(%respbyid)) {
           $responses->{$id}=join(',',@{$respbyid{$id}});
       }
       return ($errormsg,$number);
   }
   
 sub interwrite_eval {  sub interwrite_eval {
     my ($questiontitles,$responses)=@_;      my ($questiontitles,$responses)=@_;
     my $number=0;      my $number=0;
Line 7873  sub interwrite_eval { Line 10520  sub interwrite_eval {
         $id=~s/[\-\:]//g;          $id=~s/[\-\:]//g;
         $idresponses{$id}[$number]=$entries[6];          $idresponses{$id}[$number]=$entries[6];
     }      }
     foreach my $id (keys %idresponses) {      foreach my $id (keys(%idresponses)) {
        $$responses{$id}=join(',',@{$idresponses{$id}});         $$responses{$id}=join(',',@{$idresponses{$id}});
        $$responses{$id}=~s/^\s*\,//;         $$responses{$id}=~s/^\s*\,//;
     }      }
     return ($errormsg,$number);      return ($errormsg,$number);
 }  }
   
   sub turning_eval {
       my ($questiontitles,$responses)=@_;
       my $number=0;
       my $errormsg='';
       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>$number) { $number=$#entries; }
           my $id=$entries[0];
           my @idresponses;
           $id=~s/^[\#0]+//;
           unless ($id) { next; }
           for (my $idx=1;$idx<=$#entries;$idx++) {
               $entries[$idx]=~s/\,/\;/g;
               $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+\;]+//g;
               push(@idresponses,$entries[$idx]);
           }
           $$responses{$id}=join(',',@idresponses);
       }
       for (my $i=1; $i<=$number; $i++) {
           $$questiontitles[$i]=&mt('Question [_1]',$i);
       }
       return ($errormsg,$number);
   }
   
   
 sub assign_clicker_grades {  sub assign_clicker_grades {
     my ($r)=@_;      my ($r,$symb) = @_;
     my ($symb)=&get_symb($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
 # See which part we are saving to  # See which part we are saving to
     my ($partlist,$handgrade,$responseType) = &response_type($symb);      my $res_error;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
       if ($res_error) {
           return &navmap_errormsg();
       }
 # FIXME: This should probably look for the first handgradeable part  # FIXME: This should probably look for the first handgradeable part
     my $part=$$partlist[0];      my $part=$$partlist[0];
 # Start screen output  # Start screen output
     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});      my $result = &Apache::loncommon::start_data_table().
                    &Apache::loncommon::start_data_table_header_row().
     my $heading=&mt('Assigning grades based on clicker file');                   '<th>'.&mt('Assigning grades based on clicker file').'</th>'.
     $result.=(<<ENDHEADER);                   &Apache::loncommon::end_data_table_header_row().
 <br /><table width="100%" border="0"><tr><td bgcolor="#777777">                   &Apache::loncommon::start_data_table_row().'<td>';
 <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>  
 <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>  
 ENDHEADER  
 # Get correct result  # Get correct result
 # FIXME: Possibly need delimiter other than ":"  # FIXME: Possibly need delimiter other than ":"
     my @correct=();      my @correct=();
Line 7912  ENDHEADER Line 10585  ENDHEADER
                     $result.='<br /><span class="LC_warning">'.                      $result.='<br /><span class="LC_warning">'.
                              &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',                               &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
                                  $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';                                   $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
                  } elsif ($input[$i]) {                   } elsif (($input[$i]) || ($input[$i] eq '0')) {
                     $correct[$i]=$input[$i];                      $correct[$i]=$input[$i];
                  }                   }
              }               }
           }            }
        }         }
        for (my $i=0;$i<$number;$i++) {         for (my $i=0;$i<$number;$i++) {
           if (!$correct[$i]) {            if ((!$correct[$i]) && ($correct[$i] ne '0')) {
              $result.='<br /><span class="LC_error">'.               $result.='<br /><span class="LC_error">'.
                       &mt('No correct result given for question "[_1]"!',                        &mt('No correct result given for question "[_1]"!',
                           $env{'form.question:'.$i}).'</span>';                            $env{'form.question:'.$i}).'</span>';
           }            }
        }         }
        $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));         $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ((($_) || ($_ eq '0'))?$_:'-') } @correct));
     }      }
 # Start grading  # Start grading
     my $pcorrect=$env{'form.pcorrect'};      my $pcorrect=$env{'form.pcorrect'};
     my $pincorrect=$env{'form.pincorrect'};      my $pincorrect=$env{'form.pincorrect'};
     my $storecount=0;      my $storecount=0;
       my %users=();
     foreach my $key (keys(%env)) {      foreach my $key (keys(%env)) {
        my $user='';         my $user='';
        if ($key=~/^form\.student\:(.*)$/) {         if ($key=~/^form\.student\:(.*)$/) {
Line 7944  ENDHEADER Line 10618  ENDHEADER
              $user=$env{'form.multi'.$id};               $user=$env{'form.multi'.$id};
           }            }
        }         }
        if ($user) {          if ($user) {
             if ($users{$user}) {
                $result.='<br /><span class="LC_warning">'.
                         &mt('More than one entry found for [_1]!','<tt>'.$user.'</tt>').
                         '</span><br />';
             }
             $users{$user}=1; 
           my @answer=split(/\,/,$env{$key});            my @answer=split(/\,/,$env{$key});
           my $sum=0;            my $sum=0;
             my $realnumber=$number;
           for (my $i=0;$i<$number;$i++) {            for (my $i=0;$i<$number;$i++) {
              if ($answer[$i]) {               if  ($correct[$i] eq '-') {
                   $realnumber--;
                } elsif (($answer[$i]) || ($answer[$i]=~/^[0\.]+$/)) {
                 if ($gradingmechanism eq 'attendance') {                  if ($gradingmechanism eq 'attendance') {
                    $sum+=$pcorrect;                     $sum+=$pcorrect;
                   } elsif ($correct[$i] eq '*') {
                      $sum+=$pcorrect;
                 } else {                  } else {
                    if ($answer[$i] eq $correct[$i]) {  # We actually grade if correct or not
                       $sum+=$pcorrect;                     my $increment=$pincorrect;
                    } else {  # Special case: numerical answer "0"
                       $sum+=$pincorrect;                     if ($correct[$i] eq '0') {
                         if ($answer[$i]=~/^[0\.]+$/) {
                            $increment=$pcorrect;
                         }
   # General numerical answer, both evaluate to something non-zero
                      } elsif ((1.0*$correct[$i]!=0) && (1.0*$answer[$i]!=0)) {
                         if (1.0*$correct[$i]==1.0*$answer[$i]) {
                            $increment=$pcorrect;
                         }
   # Must be just alphanumeric
                      } elsif ($answer[$i] eq $correct[$i]) {
                         $increment=$pcorrect;
                    }                     }
                      $sum+=$increment;
                 }                  }
              }               }
           }            }
           my $ave=$sum/(100*$number);            my $ave=$sum/(100*$realnumber);
 # Store  # Store
           my ($username,$domain)=split(/\:/,$user);            my ($username,$domain)=split(/\:/,$user);
           my %grades=();            my %grades=();
Line 7978  ENDHEADER Line 10675  ENDHEADER
        }         }
     }      }
 # We are done  # We are done
     $result.='<br />'.&mt('Successfully stored grades for [_1] student(s).',$storecount).      $result.='<br />'.&mt('Successfully stored grades for [quant,_1,student].',$storecount).
              '</td></tr></table>'."\n".               '</td>'.
              '</td></tr></table><br /><br />'."\n";               &Apache::loncommon::end_data_table_row().
     return $result.&show_grading_menu_form($symb);               &Apache::loncommon::end_data_table();
       return $result;
   }
   
   sub navmap_errormsg {
       return '<div class="LC_error">'.
              &mt('An error occurred retrieving information about resources in the course.').'<br />'.
              &mt('It is recommended that you [_1]re-initialize the course[_2] and then return to this grading page.','<a href="/adm/roles?selectrole=1&newrole='.$env{'request.role'}.'">','</a>').
              '</div>';
   }
   
   sub startpage {
       my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js,$onload) = @_;
       my %args;
       if ($onload) {
            my %loaditems = (
                           'onload' => $onload,
                         );
            $args{'add_entries'} = \%loaditems;
       }
       if ($nomenu) {
           $args{'only_body'} = 1; 
           $r->print(&Apache::loncommon::start_page("Student's Version",$js,\%args));
       } else {
           unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
           $args{'bread_crumbs'} = $crumbs;
           $r->print(&Apache::loncommon::start_page('Grading',$js,\%args));
           if ($env{'request.course.id'}) {
               &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading'));
           }
       }
       unless ($nodisplayflag) {
           $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp));
       }
   }
   
   sub select_problem {
       my ($r)=@_;
       $r->print('<h3>'.&mt('Select the problem or one of the problems you want to grade').'</h3><form action="/adm/grades">');
       $r->print(&Apache::lonstathelpers::problem_selector('.',undef,1,undef,undef,undef,undef,1));
       $r->print('<input type="hidden" name="command" value="gradingmenu" />');
       $r->print('<input type="submit" value="'.&mt('Next').' &rarr;" /></form>');
 }  }
   
 sub handler {  sub handler {
     my $request=$_[0];      my $request=$_[0];
     &reset_caches();      &reset_caches();
     if ($env{'browser.mathml'}) {      if ($request->header_only) {
  &Apache::loncommon::content_type($request,'text/xml');          &Apache::loncommon::content_type($request,'text/html');
     } else {          $request->send_http_header;
  &Apache::loncommon::content_type($request,'text/html');          return OK;
     }      }
     $request->send_http_header;  
     return '' if $request->header_only;  
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});      &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
     my $symb=&get_symb($request,1);  
   # see what command we need to execute
   
     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];
   
       &init_perm();
       if (!$env{'request.course.id'}) {
           unless ((&Apache::lonnet::allowed('usc',$env{'request.role.domain'})) &&
                   ($command =~ /^scantronupload/)) {
               # Not in a course.
               $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context";
               return HTTP_NOT_ACCEPTABLE;
           }
       } elsif (!%perm) {
           $request->internal_redirect('/adm/quickgrades');
           return OK;
       }
       &Apache::loncommon::content_type($request,'text/html');
       $request->send_http_header;
   
     if ($#commands > 0) {      if ($#commands > 0) {
  &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));   &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
     }      }
   
   # see what the symb is
   
     $request->print(&Apache::loncommon::start_page('Grading'));      my $symb=$env{'form.symb'};
     if ($symb eq '' && $command eq '') {      unless ($symb) {
  if ($env{'user.adv'}) {         (my $url=$env{'form.url'}) =~ s-^https*://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
     if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&         $symb=&Apache::lonnet::symbread($url);
  ($env{'form.codethree'})) {      }
  my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'.      &Apache::lonenc::check_decrypt(\$symb);
     $env{'form.codethree'};  
  my ($tsymb,$tuname,$tudom,$tcrsid)=      $ssi_error = 0;
     &Apache::lonnet::checkin($token);      if (($symb eq '' || $command eq '') && ($env{'request.course.id'})) {
  if ($tsymb) {  #
     my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);  # Not called from a resource, but inside a course
     if (&Apache::lonnet::allowed('mgr',$tcrsid)) {  #    
  $request->print(&Apache::lonnet::ssi_body('/res/'.$url,          &startpage($request,undef,[],1,1);
   ('grade_username' => $tuname,          &select_problem($request);
    'grade_domain' => $tudom,  
    'grade_courseid' => $tcrsid,  
    'grade_symb' => $tsymb)));  
     } else {  
  $request->print('<h3>Not authorized: '.$token.'</h3>');  
     }  
  } else {  
     $request->print('<h3>Not a valid DocID: '.$token.'</h3>');  
  }  
     } else {  
  $request->print(&Apache::lonxml::tokeninputfield());  
     }  
  }  
     } else {      } else {
  &init_perm();  
  if ($command eq 'submission' && $perm{'vgr'}) {   if ($command eq 'submission' && $perm{'vgr'}) {
     ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));              my ($stuvcurrent,$stuvdisp,$versionform,$js);
               if (($env{'form.student'} ne '') && ($env{'form.userdom'} ne '')) {
                   ($stuvcurrent,$stuvdisp,$versionform,$js) =
                       &choose_task_version_form($symb,$env{'form.student'},
                                                 $env{'form.userdom'});
               }
               &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}],undef,undef,$stuvcurrent,$stuvdisp,undef,$js);
               if ($versionform) {
                   $request->print($versionform);
               }
               $request->print('<br clear="all" />');
       ($env{'form.student'} eq '' ? &listStudents($request,$symb) : &submission($request,0,0,$symb));
           } elsif ($command eq 'versionsub' && $perm{'vgr'}) {
               my ($stuvcurrent,$stuvdisp,$versionform,$js) =
                   &choose_task_version_form($symb,$env{'form.student'},
                                             $env{'form.userdom'},
                                             $env{'form.inhibitmenu'});
               &startpage($request,$symb,[{href=>"", text=>"Previous Student Version"}],undef,undef,$stuvcurrent,$stuvdisp,$env{'form.inhibitmenu'},$js);
               if ($versionform) {
                   $request->print($versionform);
               }
               $request->print('<br clear="all" />');
               $request->print(&show_previous_task_version($request,$symb));
  } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {   } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
     &pickStudentPage($request);              &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
                                          {href=>'',text=>'Select student'}],1,1);
       &pickStudentPage($request,$symb);
  } elsif ($command eq 'displayPage' && $perm{'vgr'}) {   } elsif ($command eq 'displayPage' && $perm{'vgr'}) {
     &displayPage($request);              &startpage($request,$symb,
                                         [{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
                                          {href=>'',text=>'Select student'},
                                          {href=>'',text=>'Grade student'}],1,1);
       &displayPage($request,$symb);
  } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {   } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
     &updateGradeByPage($request);              &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
                                          {href=>'',text=>'Select student'},
                                          {href=>'',text=>'Grade student'},
                                          {href=>'',text=>'Store grades'}],1,1);
       &updateGradeByPage($request,$symb);
  } elsif ($command eq 'processGroup' && $perm{'vgr'}) {   } elsif ($command eq 'processGroup' && $perm{'vgr'}) {
     &processGroup($request);              &startpage($request,$symb,[{href=>'',text=>'...'},
                                          {href=>'',text=>'Modify grades'}]);
       &processGroup($request,$symb);
  } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {   } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
     $request->print(&grading_menu($request));              &startpage($request,$symb);
  } elsif ($command eq 'submit_options' && $perm{'vgr'}) {      $request->print(&grading_menu($request,$symb));
     $request->print(&submit_options($request));   } elsif ($command eq 'individual' && $perm{'vgr'}) {
               &startpage($request,$symb,[{href=>'',text=>'Select individual students to grade'}]);
       $request->print(&submit_options($request,$symb));
           } elsif ($command eq 'ungraded' && $perm{'vgr'}) {
               &startpage($request,$symb,[{href=>'',text=>'Grade ungraded submissions'}]);
               $request->print(&listStudents($request,$symb,'graded'));
           } elsif ($command eq 'table' && $perm{'vgr'}) {
               &startpage($request,$symb,[{href=>"", text=>"Grading table"}]);
               $request->print(&submit_options_table($request,$symb));
           } elsif ($command eq 'all_for_one' && $perm{'vgr'}) {
               &startpage($request,$symb,[{href=>'',text=>'Grade page/folder for one student'}],1,1);
               $request->print(&submit_options_sequence($request,$symb));
  } elsif ($command eq 'viewgrades' && $perm{'vgr'}) {   } elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
     $request->print(&viewgrades($request));              &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},{href=>'', text=>"Modify grades"}]);
       $request->print(&viewgrades($request,$symb));
  } elsif ($command eq 'handgrade' && $perm{'mgr'}) {   } elsif ($command eq 'handgrade' && $perm{'mgr'}) {
     $request->print(&processHandGrade($request));              &startpage($request,$symb,[{href=>'',text=>'...'},
                                          {href=>'',text=>'Store grades'}]);
       $request->print(&processHandGrade($request,$symb));
  } elsif ($command eq 'editgrades' && $perm{'mgr'}) {   } elsif ($command eq 'editgrades' && $perm{'mgr'}) {
     $request->print(&editgrades($request));              &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},
                                          {href=>&href_symb_cmd($symb,'viewgrades').'&group=all&section=all&Status=Active',
                                                                                text=>"Modify grades"},
                                          {href=>'', text=>"Store grades"}]);
       $request->print(&editgrades($request,$symb));
           } elsif ($command eq 'initialverifyreceipt' && $perm{'vgr'}) {
               &startpage($request,$symb,[{href=>'',text=>'Verify Receipt Number'}]);
               $request->print(&initialverifyreceipt($request,$symb));
  } elsif ($command eq 'verify' && $perm{'vgr'}) {   } elsif ($command eq 'verify' && $perm{'vgr'}) {
     $request->print(&verifyreceipt($request));              &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"initialverifyreceipt"),text=>'Verify Receipt Number'},
                                          {href=>'',text=>'Verification Result'}]);
       $request->print(&verifyreceipt($request,$symb));
         } elsif ($command eq 'processclicker' && $perm{'mgr'}) {          } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
             $request->print(&process_clicker($request));              &startpage($request,$symb,[{href=>'', text=>'Process clicker'}]);
               $request->print(&process_clicker($request,$symb));
         } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {          } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
             $request->print(&process_clicker_file($request));              &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
                                          {href=>'', text=>'Process clicker file'}]);
               $request->print(&process_clicker_file($request,$symb));
         } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {          } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
             $request->print(&assign_clicker_grades($request));              &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
                                          {href=>'', text=>'Process clicker file'},
                                          {href=>'', text=>'Store grades'}]);
               $request->print(&assign_clicker_grades($request,$symb));
  } elsif ($command eq 'csvform' && $perm{'mgr'}) {   } elsif ($command eq 'csvform' && $perm{'mgr'}) {
     $request->print(&upcsvScores_form($request));              &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
       $request->print(&upcsvScores_form($request,$symb));
  } elsif ($command eq 'csvupload' && $perm{'mgr'}) {   } elsif ($command eq 'csvupload' && $perm{'mgr'}) {
     $request->print(&csvupload($request));              &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
       $request->print(&csvupload($request,$symb));
  } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {   } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
     $request->print(&csvuploadmap($request));              &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
       $request->print(&csvuploadmap($request,$symb));
  } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {   } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
     if ($env{'form.associate'} ne 'Reverse Association') {      if ($env{'form.associate'} ne 'Reverse Association') {
  $request->print(&csvuploadoptions($request));                  &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
    $request->print(&csvuploadoptions($request,$symb));
     } else {      } else {
  if ( $env{'form.upfile_associate'} ne 'reverse' ) {   if ( $env{'form.upfile_associate'} ne 'reverse' ) {
     $env{'form.upfile_associate'} = 'reverse';      $env{'form.upfile_associate'} = 'reverse';
  } else {   } else {
     $env{'form.upfile_associate'} = 'forward';      $env{'form.upfile_associate'} = 'forward';
  }   }
  $request->print(&csvuploadmap($request));                  &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
    $request->print(&csvuploadmap($request,$symb));
     }      }
  } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {   } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
     $request->print(&csvuploadassign($request));              &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
       $request->print(&csvuploadassign($request,$symb));
  } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {   } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
     $request->print(&scantron_selectphase($request));              &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1,
                          undef,undef,undef,undef,'toggleScantab(document.rules);');
       $request->print(&scantron_selectphase($request,undef,$symb));
   } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {    } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
      $request->print(&scantron_do_warning($request));              &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
        $request->print(&scantron_do_warning($request,$symb));
  } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {   } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
     $request->print(&scantron_validate_file($request));              &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
       $request->print(&scantron_validate_file($request,$symb));
  } elsif ($command eq 'scantron_process' && $perm{'mgr'}) {   } elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
     $request->print(&scantron_process_students($request));              &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
       $request->print(&scantron_process_students($request,$symb));
   } elsif ($command eq 'scantronupload' &&     } elsif ($command eq 'scantronupload' && 
   (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||    (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
   &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {    &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
      $request->print(&scantron_upload_scantron_data($request));               &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1,
                          undef,undef,undef,undef,'toggleScantab(document.rules);');
        $request->print(&scantron_upload_scantron_data($request,$symb)); 
   } elsif ($command eq 'scantronupload_save' &&    } elsif ($command eq 'scantronupload_save' &&
   (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||    (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
   &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {    &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
      $request->print(&scantron_upload_scantron_data_save($request));              &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
        $request->print(&scantron_upload_scantron_data_save($request,$symb));
   } elsif ($command eq 'scantron_download' &&    } elsif ($command eq 'scantron_download' &&
  &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {   &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
      $request->print(&scantron_download_scantron_data($request));              &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
        $request->print(&scantron_download_scantron_data($request,$symb));
           } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) {
               &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
               $request->print(&checkscantron_results($request,$symb));
           } elsif ($command eq 'downloadfilesselect' && $perm{'vgr'}) {
               &startpage($request,$symb,[{href=>'', text=>'Select which submissions to download'}]);
               $request->print(&submit_options_download($request,$symb));
            } elsif ($command eq 'downloadfileslink' && $perm{'vgr'}) {
               &startpage($request,$symb,
      [{href=>&href_symb_cmd($symb,'downloadfilesselect'), text=>'Select which submissions to download'},
       {href=>'', text=>'Download submitted files'}]);
               &submit_download_link($request,$symb);
  } elsif ($command) {   } elsif ($command) {
     $request->print("Access Denied ($command)");              &startpage($request,$symb,[{href=>'', text=>'Access denied'}]);
       $request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
  }   }
     }      }
     $request->print(&Apache::loncommon::end_page());      if ($ssi_error) {
    &ssi_print_error($request);
       }
       if ($env{'form.inhibitmenu'}) {
           $request->print(&Apache::loncommon::end_page());
       } elsif ($env{'request.course.id'}) {
           &Apache::lonquickgrades::endGradeScreen($request);
       }
     &reset_caches();      &reset_caches();
     return '';      return OK;
 }  }
   
 1;  1;
   
 __END__;  __END__;
   
   
   =head1 NAME
   
   Apache::grades
   
   =head1 SYNOPSIS
   
   Handles the viewing of grades.
   
   This is part of the LearningOnline Network with CAPA project
   described at http://www.lon-capa.org.
   
   =head1 OVERVIEW
   
   Do an ssi with retries:
   While I'd love to factor out this with the version in lonprintout,
   that would either require a data coupling between modules, which I refuse to perpetuate (there's quite enough of that already), or would require the invention of another infrastructure
   I'm not quite ready to invent (e.g. an ssi_with_retry object).
   
   At least the logic that drives this has been pulled out into loncommon.
   
   
   
   ssi_with_retries - Does the server side include of a resource.
                        if the ssi call returns an error we'll retry it up to
                        the number of times requested by the caller.
                        If we still have a problem, no text is appended to the
                        output and we set some global variables.
                        to indicate to the caller an SSI error occurred.  
                        All of this is supposed to deal with the issues described
                        in LON-CAPA BZ 5631 see:
                        http://bugs.lon-capa.org/show_bug.cgi?id=5631
                        by informing the user that this happened.
   
   Parameters:
     resource   - The resource to include.  This is passed directly, without
                  interpretation to lonnet::ssi.
     form       - The form hash parameters that guide the interpretation of the resource
                  
     retries    - Number of retries allowed before giving up completely.
   Returns:
     On success, returns the rendered resource identified by the resource parameter.
   Side Effects:
     The following global variables can be set:
      ssi_error                - If an unrecoverable error occurred this becomes true.
                                 It is up to the caller to initialize this to false
                                 if desired.
      ssi_error_resource  - If an unrecoverable error occurred, this is the value
                                 of the resource that could not be rendered by the ssi
                                 call.
      ssi_error_message   - The error string fetched from the ssi response
                                 in the event of an error.
   
   
   =head1 HANDLER SUBROUTINE
   
   ssi_with_retries()
   
   =head1 SUBROUTINES
   
   =over
   
   =head1 Routines to display previous version of a Task for a specific student
   
   Tasks are graded pass/fail. Students who have yet to pass a particular Task
   can receive another opportunity. Access to tasks is slot-based. If a slot
   requires a proctor to check-in the student, a new version of the Task will
   be created when the student is checked in to the new opportunity.
   
   If a particular student has tried two or more versions of a particular task,
   the submission screen provides a user with vgr privileges (e.g., a Course
   Coordinator) the ability to display a previous version worked on by the
   student.  By default, the current version is displayed. If a previous version
   has been selected for display, submission data are only shown that pertain
   to that particular version, and the interface to submit grades is not shown.
   
   =over 4
   
   =item show_previous_task_version()
   
   Displays a specified version of a student's Task, as the student sees it.
   
   Inputs: 2
           request - request object
           symb    - unique symb for current instance of resource
   
   Output: None.
   
   Side Effects: calls &show_problem() to print version of Task, with
                 version contained in form item: $env{'form.previousversion'}
   
   =item choose_task_version_form()
   
   Displays a web form used to select which version of a student's view of a
   Task should be displayed.  Either launches a pop-up window, or replaces
   content in existing pop-up, or replaces page in main window.
   
   Inputs: 4
           symb    - unique symb for current instance of resource
           uname   - username of student
           udom    - domain of student
           nomenu  - 1 if display is in a pop-up window, and hence no menu
                     breadcrumbs etc., are displayed
   
   Output: 4
           current   - student's current version
           displayed - student's version being displayed
           result    - scalar containing HTML for web form used to switch to
                       a different version (or a link to close window, if pop-up).
           js        - javascript for processing selection in versions web form
   
   Side Effects: None.
   
   =item previous_display_javascript()
   
   Inputs: 2
           nomenu  - 1 if display is in a pop-up window, and hence no menu
                     breadcrumbs etc., are displayed.
           current - student's current version number.
   
   Output: 1
           js      - javascript for processing selection in versions web form.
   
   Side Effects: None.
   
   =back
   
   =head1 Routines to process bubblesheet data.
   
   =over 4
   
   =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 &Apache::lonnet::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
   
      $randomorder - True if exam folder has randomorder set
      $randompick  - True if exam folder has randompick set
      $respnumlookup - Reference to HASH mapping question numbers in bubble lines
                        for current line to question number used for same question
                        in "Master Seqence" (as seen by Course Coordinator).
      $startline   - Reference to hash where key is question number (0 is first)
                     and value is number of first bubble line for current student
                     or code-based randompick and/or randomorder.
   
   
   
   =item  scantron_get_maxbubble() : 
   
      Arguments:
          $nav_error  - Reference to scalar which is a flag to indicate a
                         failure to retrieve a navmap object.
          if $nav_error is set to 1 by scantron_get_maxbubble(), the 
          calling routine should trap the error condition and display the warning
          found in &navmap_errormsg().
   
          $scantron_config - Reference to bubblesheet format configuration hash.
   
      Returns the maximum number of bubble lines that are expected to
      occur. Does this by walking the selected sequence rendering the
      resource and then checking &Apache::lonxml::get_problem_counter()
      for what the current value of the problem counter is.
   
      Caches the results to $env{'form.scantron_maxbubble'},
      $env{'form.scantron.bubble_lines.n'}, 
      $env{'form.scantron.first_bubble_line.n'} and
      $env{"form.scantron.sub_bubblelines.n"}
      which are the total number of bubble lines, the number of bubble
      lines for response n and number of the first bubble line for response n,
      and a comma separated list of numbers of bubble lines for sub-questions
      (for optionresponse, matchresponse, and rankresponse items), for response n.  
   
   
   =item  scantron_validate_missingbubbles() : 
   
      Validates all scanlines in the selected file to not have any
       answers that don't have bubbles that have not been verified
       to be bubble free.
   
   =item  scantron_process_students() : 
   
      Routine that does the actual grading of the bubblesheet information.
   
      The parsed scanline hash is added to %env 
   
      Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
      foreach resource , with the form data of
   
    'submitted'     =>'scantron' 
    'grade_target'  =>'grade',
    'grade_username'=> username of student
    'grade_domain'  => domain of student
    'grade_courseid'=> of course
    'grade_symb'    => symb of resource to grade
   
       This triggers a grading pass. The problem grading code takes care
       of converting the bubbled letter information (now in %env) into a
       valid submission.
   
   =item  scantron_upload_scantron_data() :
   
       Creates the screen for adding a new bubblesheet data file to a course.
   
   =item  scantron_upload_scantron_data_save() : 
   
      Adds a provided bubble information data file to the course if user
      has the correct privileges to do so. 
   
   =item  valid_file() :
   
      Validates that the requested bubble data file exists in the course.
   
   =item  scantron_download_scantron_data() : 
   
      Shows a list of the three internal files (original, corrected,
      skipped) for a specific bubblesheet data file that exists in the
      course.
   
   =item  scantron_validate_ID() : 
   
      Validates all scanlines in the selected file to not have any
      invalid or underspecified student/employee IDs
   
   =item navmap_errormsg() :
   
      Returns HTML mark-up inside a <div></div> with a link to re-initialize the course.
      Should be called whenever the request to instantiate a navmap object fails.
   
   =back
   
   =back
   
   =cut

Removed from v.1.465  
changed lines
  Added in v.1.766


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